summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-05-05 04:07:10 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2017-05-05 04:15:35 +0200
commitaf82455f7dbd9dc20244d80d033721b30d22c065 (patch)
tree3b9246456e82ae116b57834a2f0b4a307a016474 /drivers
parentMerge tag 'driver-core-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel... (diff)
parentfirmware: google memconsole: Fix return value check in platform_memconsole_in... (diff)
downloadlinux-af82455f7dbd9dc20244d80d033721b30d22c065.tar.xz
linux-af82455f7dbd9dc20244d80d033721b30d22c065.zip
Merge tag 'char-misc-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char/misc driver updates from Greg KH: "Here is the big set of new char/misc driver drivers and features for 4.12-rc1. There's lots of new drivers added this time around, new firmware drivers from Google, more auxdisplay drivers, extcon drivers, fpga drivers, and a bunch of other driver updates. Nothing major, except if you happen to have the hardware for these drivers, and then you will be happy :) All of these have been in linux-next for a while with no reported issues" * tag 'char-misc-4.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (136 commits) firmware: google memconsole: Fix return value check in platform_memconsole_init() firmware: Google VPD: Fix return value check in vpd_platform_init() goldfish_pipe: fix build warning about using too much stack. goldfish_pipe: An implementation of more parallel pipe fpga fr br: update supported version numbers fpga: region: release FPGA region reference in error path fpga altera-hps2fpga: disable/unprepare clock on error in alt_fpga_bridge_probe() mei: drop the TODO from samples firmware: Google VPD sysfs driver firmware: Google VPD: import lib_vpd source files misc: lkdtm: Add volatile to intentional NULL pointer reference eeprom: idt_89hpesx: Add OF device ID table misc: ds1682: Add OF device ID table misc: tsl2550: Add OF device ID table w1: Remove unneeded use of assert() and remove w1_log.h w1: Use kernel common min() implementation uio_mf624: Align memory regions to page size and set correct offsets uio_mf624: Refactor memory info initialization uio: Allow handling of non page-aligned memory regions hangcheck-timer: Fix typo in comment ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/android/Kconfig2
-rw-r--r--drivers/auxdisplay/Kconfig304
-rw-r--r--drivers/auxdisplay/Makefile4
-rw-r--r--drivers/auxdisplay/arm-charlcd.c (renamed from drivers/misc/arm-charlcd.c)0
-rw-r--r--drivers/auxdisplay/charlcd.c818
-rw-r--r--drivers/auxdisplay/hd44780.c326
-rw-r--r--drivers/auxdisplay/ht16k33.c20
-rw-r--r--drivers/auxdisplay/img-ascii-lcd.c1
-rw-r--r--drivers/auxdisplay/panel.c (renamed from drivers/misc/panel.c)827
-rw-r--r--drivers/char/hangcheck-timer.c2
-rw-r--r--drivers/char/hpet.c2
-rw-r--r--drivers/char/misc.c11
-rw-r--r--drivers/char/mspec.c9
-rw-r--r--drivers/char/tpm/tpm-chip.c54
-rw-r--r--drivers/char/virtio_console.c2
-rw-r--r--drivers/dax/dax.c33
-rw-r--r--drivers/extcon/Kconfig7
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-arizona.c46
-rw-r--r--drivers/extcon/extcon-intel-cht-wc.c395
-rw-r--r--drivers/extcon/extcon-palmas.c6
-rw-r--r--drivers/extcon/extcon-usb-gpio.c10
-rw-r--r--drivers/extcon/extcon.c22
-rw-r--r--drivers/firewire/core-topology.c2
-rw-r--r--drivers/firewire/core.h8
-rw-r--r--drivers/firmware/google/Kconfig59
-rw-r--r--drivers/firmware/google/Makefile10
-rw-r--r--drivers/firmware/google/coreboot_table-acpi.c88
-rw-r--r--drivers/firmware/google/coreboot_table-of.c82
-rw-r--r--drivers/firmware/google/coreboot_table.c94
-rw-r--r--drivers/firmware/google/coreboot_table.h50
-rw-r--r--drivers/firmware/google/memconsole-coreboot.c109
-rw-r--r--drivers/firmware/google/memconsole-x86-legacy.c153
-rw-r--r--drivers/firmware/google/memconsole.c155
-rw-r--r--drivers/firmware/google/memconsole.h43
-rw-r--r--drivers/firmware/google/vpd.c332
-rw-r--r--drivers/firmware/google/vpd_decode.c99
-rw-r--r--drivers/firmware/google/vpd_decode.h58
-rw-r--r--drivers/fpga/Kconfig42
-rw-r--r--drivers/fpga/Makefile6
-rw-r--r--drivers/fpga/altera-freeze-bridge.c32
-rw-r--r--drivers/fpga/altera-hps2fpga.c15
-rw-r--r--drivers/fpga/altera-pr-ip-core-plat.c68
-rw-r--r--drivers/fpga/altera-pr-ip-core.c220
-rw-r--r--drivers/fpga/fpga-bridge.c17
-rw-r--r--drivers/fpga/fpga-mgr.c2
-rw-r--r--drivers/fpga/fpga-region.c15
-rw-r--r--drivers/fpga/ice40-spi.c207
-rw-r--r--drivers/fpga/ts73xx-fpga.c156
-rw-r--r--drivers/fpga/xilinx-pr-decoupler.c161
-rw-r--r--drivers/fpga/xilinx-spi.c198
-rw-r--r--drivers/fpga/zynq-fpga.c28
-rw-r--r--drivers/gpio/gpiolib.c23
-rw-r--r--drivers/hv/channel.c10
-rw-r--r--drivers/hv/channel_mgmt.c48
-rw-r--r--drivers/hv/connection.c65
-rw-r--r--drivers/hv/hv.c5
-rw-r--r--drivers/hv/hv_balloon.c2
-rw-r--r--drivers/hv/hv_fcopy.c2
-rw-r--r--drivers/hv/hv_kvp.c12
-rw-r--r--drivers/hv/hv_snapshot.c2
-rw-r--r--drivers/hv/hyperv_vmbus.h29
-rw-r--r--drivers/hv/ring_buffer.c22
-rw-r--r--drivers/hv/vmbus_drv.c4
-rw-r--r--drivers/hwtracing/coresight/of_coresight.c2
-rw-r--r--drivers/iio/industrialio-core.c15
-rw-r--r--drivers/infiniband/core/ucm.c35
-rw-r--r--drivers/infiniband/core/user_mad.c4
-rw-r--r--drivers/infiniband/core/uverbs_main.c2
-rw-r--r--drivers/infiniband/hw/hfi1/device.c2
-rw-r--r--drivers/input/evdev.c11
-rw-r--r--drivers/input/joydev.c11
-rw-r--r--drivers/input/mousedev.c11
-rw-r--r--drivers/media/cec/cec-core.c16
-rw-r--r--drivers/media/media-devnode.c20
-rw-r--r--drivers/misc/Kconfig293
-rw-r--r--drivers/misc/Makefile5
-rw-r--r--drivers/misc/aspeed-lpc-ctrl.c266
-rw-r--r--drivers/misc/ds1682.c7
-rw-r--r--drivers/misc/eeprom/idt_89hpesx.c57
-rw-r--r--drivers/misc/lkdtm.h1
-rw-r--r--drivers/misc/lkdtm_bugs.c13
-rw-r--r--drivers/misc/lkdtm_core.c1
-rw-r--r--drivers/misc/mei/Makefile1
-rw-r--r--drivers/misc/mei/amthif.c340
-rw-r--r--drivers/misc/mei/bus-fixup.c9
-rw-r--r--drivers/misc/mei/bus.c6
-rw-r--r--drivers/misc/mei/client.c14
-rw-r--r--drivers/misc/mei/client.h5
-rw-r--r--drivers/misc/mei/hbm.c29
-rw-r--r--drivers/misc/mei/init.c14
-rw-r--r--drivers/misc/mei/interrupt.c38
-rw-r--r--drivers/misc/mei/main.c125
-rw-r--r--drivers/misc/mei/mei_dev.h51
-rw-r--r--drivers/misc/mei/pci-me.c31
-rw-r--r--drivers/misc/mei/pci-txe.c29
-rw-r--r--drivers/misc/tsl2550.c7
-rw-r--r--drivers/mtd/ubi/build.c91
-rw-r--r--drivers/mtd/ubi/vmt.c49
-rw-r--r--drivers/nvmem/Kconfig11
-rw-r--r--drivers/nvmem/Makefile2
-rw-r--r--drivers/nvmem/core.c3
-rw-r--r--drivers/nvmem/imx-iim.c173
-rw-r--r--drivers/nvmem/imx-ocotp.c254
-rw-r--r--drivers/nvmem/sunxi_sid.c89
-rw-r--r--drivers/platform/chrome/cros_ec_dev.c31
-rw-r--r--drivers/platform/goldfish/goldfish_pipe.c970
-rw-r--r--drivers/pps/pps.c123
-rw-r--r--drivers/rapidio/devices/rio_mport_cdev.c24
-rw-r--r--drivers/rapidio/rio-sysfs.c76
-rw-r--r--drivers/rapidio/rio.c3
-rw-r--r--drivers/rapidio/rio.h2
-rw-r--r--drivers/rtc/class.c14
-rw-r--r--drivers/rtc/rtc-core.h10
-rw-r--r--drivers/rtc/rtc-dev.c17
-rw-r--r--drivers/scsi/osd/osd_uld.c59
-rw-r--r--drivers/uio/uio.c2
-rw-r--r--drivers/uio/uio_mf624.c48
-rw-r--r--drivers/vme/vme.c469
-rw-r--r--drivers/w1/masters/matrox_w1.c6
-rw-r--r--drivers/w1/slaves/Kconfig6
-rw-r--r--drivers/w1/slaves/Makefile1
-rw-r--r--drivers/w1/slaves/w1_ds2438.c390
-rw-r--r--drivers/w1/slaves/w1_ds2760.h10
-rw-r--r--drivers/w1/w1.c1
-rw-r--r--drivers/w1/w1_family.h1
-rw-r--r--drivers/w1/w1_int.c1
-rw-r--r--drivers/w1/w1_io.c1
-rw-r--r--drivers/w1/w1_log.h31
-rw-r--r--drivers/w1/w1_netlink.c5
-rw-r--r--drivers/zorro/zorro-driver.c15
-rw-r--r--drivers/zorro/zorro-sysfs.c76
-rw-r--r--drivers/zorro/zorro.c3
-rw-r--r--drivers/zorro/zorro.h3
134 files changed, 7337 insertions, 2839 deletions
diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig
index a82fc022d34b..832e885349b1 100644
--- a/drivers/android/Kconfig
+++ b/drivers/android/Kconfig
@@ -22,7 +22,7 @@ config ANDROID_BINDER_IPC
config ANDROID_BINDER_DEVICES
string "Android Binder devices"
depends on ANDROID_BINDER_IPC
- default "binder"
+ default "binder,hwbinder"
---help---
Default value for the binder.devices parameter.
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index 8a8e403644d6..9ae6681c90ad 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -13,8 +13,22 @@ menuconfig AUXDISPLAY
If you say N, all options in this submenu will be skipped and disabled.
+config CHARLCD
+ tristate "Character LCD core support" if COMPILE_TEST
+
if AUXDISPLAY
+config HD44780
+ tristate "HD44780 Character LCD support"
+ depends on GPIOLIB || COMPILE_TEST
+ select CHARLCD
+ ---help---
+ Enable support for Character LCDs using a HD44780 controller.
+ The LCD is accessible through the /dev/lcd char device (10, 156).
+ This code can either be compiled as a module, or linked into the
+ kernel and started at boot.
+ If you don't understand what all this is about, say N.
+
config KS0108
tristate "KS0108 LCD Controller"
depends on PARPORT_PC
@@ -142,3 +156,293 @@ config HT16K33
LED controller driver with keyscan.
endif # AUXDISPLAY
+
+config ARM_CHARLCD
+ bool "ARM Ltd. Character LCD Driver"
+ depends on PLAT_VERSATILE
+ help
+ This is a driver for the character LCD found on the ARM Ltd.
+ Versatile and RealView Platform Baseboards. It doesn't do
+ very much more than display the text "ARM Linux" on the first
+ line and the Linux version on the second line, but that's
+ still useful.
+
+config PANEL
+ tristate "Parallel port LCD/Keypad Panel support"
+ depends on PARPORT
+ select CHARLCD
+ ---help---
+ Say Y here if you have an HD44780 or KS-0074 LCD connected to your
+ parallel port. This driver also features 4 and 6-key keypads. The LCD
+ is accessible through the /dev/lcd char device (10, 156), and the
+ keypad through /dev/keypad (10, 185). This code can either be
+ compiled as a module, or linked into the kernel and started at boot.
+ If you don't understand what all this is about, say N.
+
+if PANEL
+
+config PANEL_PARPORT
+ int "Default parallel port number (0=LPT1)"
+ range 0 255
+ default "0"
+ ---help---
+ This is the index of the parallel port the panel is connected to. One
+ driver instance only supports one parallel port, so if your keypad
+ and LCD are connected to two separate ports, you have to start two
+ modules with different arguments. Numbering starts with '0' for LPT1,
+ and so on.
+
+config PANEL_PROFILE
+ int "Default panel profile (0-5, 0=custom)"
+ range 0 5
+ default "5"
+ ---help---
+ To ease configuration, the driver supports different configuration
+ profiles for past and recent wirings. These profiles can also be
+ used to define an approximative configuration, completed by a few
+ other options. Here are the profiles :
+
+ 0 = custom (see further)
+ 1 = 2x16 parallel LCD, old keypad
+ 2 = 2x16 serial LCD (KS-0074), new keypad
+ 3 = 2x16 parallel LCD (Hantronix), no keypad
+ 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad
+ 5 = 2x40 parallel LCD (old one), with old keypad
+
+ Custom configurations allow you to define how your display is
+ wired to the parallel port, and how it works. This is only intended
+ for experts.
+
+config PANEL_KEYPAD
+ depends on PANEL_PROFILE="0"
+ int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
+ range 0 3
+ default 0
+ ---help---
+ This enables and configures a keypad connected to the parallel port.
+ The keys will be read from character device 10,185. Valid values are :
+
+ 0 : do not enable this driver
+ 1 : old 6 keys keypad
+ 2 : new 6 keys keypad, as used on the server at www.ant-computing.com
+ 3 : Nexcom NSA1045's 4 keys keypad
+
+ New profiles can be described in the driver source. The driver also
+ supports simultaneous keys pressed when the keypad supports them.
+
+config PANEL_LCD
+ depends on PANEL_PROFILE="0"
+ int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
+ range 0 5
+ default 0
+ ---help---
+ This enables and configures an LCD connected to the parallel port.
+ The driver includes an interpreter for escape codes starting with
+ '\e[L' which are specific to the LCD, and a few ANSI codes. The
+ driver will be registered as character device 10,156, usually
+ under the name '/dev/lcd'. There are a total of 6 supported types :
+
+ 0 : do not enable the driver
+ 1 : custom configuration and wiring (see further)
+ 2 : 2x16 & 2x40 parallel LCD (old wiring)
+ 3 : 2x16 serial LCD (KS-0074 based)
+ 4 : 2x16 parallel LCD (Hantronix wiring)
+ 5 : 2x16 parallel LCD (Nexcom wiring)
+
+ When type '1' is specified, other options will appear to configure
+ more precise aspects (wiring, dimensions, protocol, ...). Please note
+ that those values changed from the 2.4 driver for better consistency.
+
+config PANEL_LCD_HEIGHT
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "Number of lines on the LCD (1-2)"
+ range 1 2
+ default 2
+ ---help---
+ This is the number of visible character lines on the LCD in custom profile.
+ It can either be 1 or 2.
+
+config PANEL_LCD_WIDTH
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "Number of characters per line on the LCD (1-40)"
+ range 1 40
+ default 40
+ ---help---
+ This is the number of characters per line on the LCD in custom profile.
+ Common values are 16,20,24,40.
+
+config PANEL_LCD_BWIDTH
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "Internal LCD line width (1-40, 40 by default)"
+ range 1 40
+ default 40
+ ---help---
+ Most LCDs use a standard controller which supports hardware lines of 40
+ characters, although sometimes only 16, 20 or 24 of them are really wired
+ to the terminal. This results in some non-visible but addressable characters,
+ and is the case for most parallel LCDs. Other LCDs, and some serial ones,
+ however, use the same line width internally as what is visible. The KS0074
+ for example, uses 16 characters per line for 16 visible characters per line.
+
+ This option lets you configure the value used by your LCD in 'custom' profile.
+ If you don't know, put '40' here.
+
+config PANEL_LCD_HWIDTH
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "Hardware LCD line width (1-64, 64 by default)"
+ range 1 64
+ default 64
+ ---help---
+ Most LCDs use a single address bit to differentiate line 0 and line 1. Since
+ some of them need to be able to address 40 chars with the lower bits, they
+ often use the immediately superior power of 2, which is 64, to address the
+ next line.
+
+ If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and
+ 64 here for a 2x40.
+
+config PANEL_LCD_CHARSET
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "LCD character set (0=normal, 1=KS0074)"
+ range 0 1
+ default 0
+ ---help---
+ Some controllers such as the KS0074 use a somewhat strange character set
+ where many symbols are at unusual places. The driver knows how to map
+ 'standard' ASCII characters to the character sets used by these controllers.
+ Valid values are :
+
+ 0 : normal (untranslated) character set
+ 1 : KS0074 character set
+
+ If you don't know, use the normal one (0).
+
+config PANEL_LCD_PROTO
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "LCD communication mode (0=parallel 8 bits, 1=serial)"
+ range 0 1
+ default 0
+ ---help---
+ This driver now supports any serial or parallel LCD wired to a parallel
+ port. But before assigning signals, the driver needs to know if it will
+ be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires
+ (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals
+ (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits
+ parallel LCD, and 1 for a serial LCD.
+
+config PANEL_LCD_PIN_E
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
+ int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
+ range -17 17
+ default 14
+ ---help---
+ This describes the number of the parallel port pin to which the LCD 'E'
+ signal has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'E' pin in custom profile is '14' (AUTOFEED).
+
+config PANEL_LCD_PIN_RS
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
+ int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
+ range -17 17
+ default 17
+ ---help---
+ This describes the number of the parallel port pin to which the LCD 'RS'
+ signal has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'RS' pin in custom profile is '17' (SELECT IN).
+
+config PANEL_LCD_PIN_RW
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
+ int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
+ range -17 17
+ default 16
+ ---help---
+ This describes the number of the parallel port pin to which the LCD 'RW'
+ signal has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'RW' pin in custom profile is '16' (INIT).
+
+config PANEL_LCD_PIN_SCL
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
+ int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
+ range -17 17
+ default 1
+ ---help---
+ This describes the number of the parallel port pin to which the serial
+ LCD 'SCL' signal has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'SCL' pin in custom profile is '1' (STROBE).
+
+config PANEL_LCD_PIN_SDA
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
+ int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
+ range -17 17
+ default 2
+ ---help---
+ This describes the number of the parallel port pin to which the serial
+ LCD 'SDA' signal has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'SDA' pin in custom profile is '2' (D0).
+
+config PANEL_LCD_PIN_BL
+ depends on PANEL_PROFILE="0" && PANEL_LCD="1"
+ int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
+ range -17 17
+ default 0
+ ---help---
+ This describes the number of the parallel port pin to which the LCD 'BL' signal
+ has been connected. It can be :
+
+ 0 : no connection (eg: connected to ground)
+ 1..17 : directly connected to any of these pins on the DB25 plug
+ -1..-17 : connected to the same pin through an inverter (eg: transistor).
+
+ Default for the 'BL' pin in custom profile is '0' (uncontrolled).
+
+config PANEL_CHANGE_MESSAGE
+ bool "Change LCD initialization message ?"
+ default "n"
+ ---help---
+ This allows you to replace the boot message indicating the kernel version
+ and the driver version with a custom message. This is useful on appliances
+ where a simple 'Starting system' message can be enough to stop a customer
+ from worrying.
+
+ If you say 'Y' here, you'll be able to choose a message yourself. Otherwise,
+ say 'N' and keep the default message with the version.
+
+config PANEL_BOOT_MESSAGE
+ depends on PANEL_CHANGE_MESSAGE="y"
+ string "New initialization message"
+ default ""
+ ---help---
+ This allows you to replace the boot message indicating the kernel version
+ and the driver version with a custom message. This is useful on appliances
+ where a simple 'Starting system' message can be enough to stop a customer
+ from worrying.
+
+ An empty message will only clear the display at driver init time. Any other
+ printf()-formatted message is valid with newline and escape codes.
+
+endif # PANEL
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index cb3dd847713b..2b8af3dc5e42 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -2,7 +2,11 @@
# Makefile for the kernel auxiliary displays device drivers.
#
+obj-$(CONFIG_CHARLCD) += charlcd.o
+obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_KS0108) += ks0108.o
obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
+obj-$(CONFIG_HD44780) += hd44780.o
obj-$(CONFIG_HT16K33) += ht16k33.o
+obj-$(CONFIG_PANEL) += panel.o
diff --git a/drivers/misc/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c
index b3176ee92b90..b3176ee92b90 100644
--- a/drivers/misc/arm-charlcd.c
+++ b/drivers/auxdisplay/arm-charlcd.c
diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c
new file mode 100644
index 000000000000..cfeb049a01ef
--- /dev/null
+++ b/drivers/auxdisplay/charlcd.c
@@ -0,0 +1,818 @@
+/*
+ * Character LCD driver for Linux
+ *
+ * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
+ * Copyright (C) 2016-2017 Glider bvba
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+#include <generated/utsrelease.h>
+
+#include <misc/charlcd.h>
+
+#define LCD_MINOR 156
+
+#define DEFAULT_LCD_BWIDTH 40
+#define DEFAULT_LCD_HWIDTH 64
+
+/* Keep the backlight on this many seconds for each flash */
+#define LCD_BL_TEMPO_PERIOD 4
+
+#define LCD_FLAG_B 0x0004 /* Blink on */
+#define LCD_FLAG_C 0x0008 /* Cursor on */
+#define LCD_FLAG_D 0x0010 /* Display on */
+#define LCD_FLAG_F 0x0020 /* Large font mode */
+#define LCD_FLAG_N 0x0040 /* 2-rows mode */
+#define LCD_FLAG_L 0x0080 /* Backlight enabled */
+
+/* LCD commands */
+#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
+
+#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
+#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
+
+#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
+#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
+#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
+#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
+
+#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
+#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
+#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
+
+#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
+#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
+#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
+#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
+
+#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
+
+#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
+
+#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
+#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
+
+struct charlcd_priv {
+ struct charlcd lcd;
+
+ struct delayed_work bl_work;
+ struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
+ bool bl_tempo;
+
+ bool must_clear;
+
+ /* contains the LCD config state */
+ unsigned long int flags;
+
+ /* Contains the LCD X and Y offset */
+ struct {
+ unsigned long int x;
+ unsigned long int y;
+ } addr;
+
+ /* Current escape sequence and it's length or -1 if outside */
+ struct {
+ char buf[LCD_ESCAPE_LEN + 1];
+ int len;
+ } esc_seq;
+
+ unsigned long long drvdata[0];
+};
+
+#define to_priv(p) container_of(p, struct charlcd_priv, lcd)
+
+/* Device single-open policy control */
+static atomic_t charlcd_available = ATOMIC_INIT(1);
+
+/* sleeps that many milliseconds with a reschedule */
+static void long_sleep(int ms)
+{
+ if (in_interrupt())
+ mdelay(ms);
+ else
+ schedule_timeout_interruptible(msecs_to_jiffies(ms));
+}
+
+/* turn the backlight on or off */
+static void charlcd_backlight(struct charlcd *lcd, int on)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ if (!lcd->ops->backlight)
+ return;
+
+ mutex_lock(&priv->bl_tempo_lock);
+ if (!priv->bl_tempo)
+ lcd->ops->backlight(lcd, on);
+ mutex_unlock(&priv->bl_tempo_lock);
+}
+
+static void charlcd_bl_off(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct charlcd_priv *priv =
+ container_of(dwork, struct charlcd_priv, bl_work);
+
+ mutex_lock(&priv->bl_tempo_lock);
+ if (priv->bl_tempo) {
+ priv->bl_tempo = false;
+ if (!(priv->flags & LCD_FLAG_L))
+ priv->lcd.ops->backlight(&priv->lcd, 0);
+ }
+ mutex_unlock(&priv->bl_tempo_lock);
+}
+
+/* turn the backlight on for a little while */
+void charlcd_poke(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ if (!lcd->ops->backlight)
+ return;
+
+ cancel_delayed_work_sync(&priv->bl_work);
+
+ mutex_lock(&priv->bl_tempo_lock);
+ if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
+ lcd->ops->backlight(lcd, 1);
+ priv->bl_tempo = true;
+ schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
+ mutex_unlock(&priv->bl_tempo_lock);
+}
+EXPORT_SYMBOL_GPL(charlcd_poke);
+
+static void charlcd_gotoxy(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+ unsigned int addr;
+
+ /*
+ * we force the cursor to stay at the end of the
+ * line if it wants to go farther
+ */
+ addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1)
+ : lcd->bwidth - 1;
+ if (priv->addr.y & 1)
+ addr += lcd->hwidth;
+ if (priv->addr.y & 2)
+ addr += lcd->bwidth;
+ lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr);
+}
+
+static void charlcd_home(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ priv->addr.x = 0;
+ priv->addr.y = 0;
+ charlcd_gotoxy(lcd);
+}
+
+static void charlcd_print(struct charlcd *lcd, char c)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ if (priv->addr.x < lcd->bwidth) {
+ if (lcd->char_conv)
+ c = lcd->char_conv[(unsigned char)c];
+ lcd->ops->write_data(lcd, c);
+ priv->addr.x++;
+ }
+ /* prevents the cursor from wrapping onto the next line */
+ if (priv->addr.x == lcd->bwidth)
+ charlcd_gotoxy(lcd);
+}
+
+static void charlcd_clear_fast(struct charlcd *lcd)
+{
+ int pos;
+
+ charlcd_home(lcd);
+
+ if (lcd->ops->clear_fast)
+ lcd->ops->clear_fast(lcd);
+ else
+ for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++)
+ lcd->ops->write_data(lcd, ' ');
+
+ charlcd_home(lcd);
+}
+
+/* clears the display and resets X/Y */
+static void charlcd_clear_display(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR);
+ priv->addr.x = 0;
+ priv->addr.y = 0;
+ /* we must wait a few milliseconds (15) */
+ long_sleep(15);
+}
+
+static int charlcd_init_display(struct charlcd *lcd)
+{
+ void (*write_cmd_raw)(struct charlcd *lcd, int cmd);
+ struct charlcd_priv *priv = to_priv(lcd);
+ u8 init;
+
+ if (lcd->ifwidth != 4 && lcd->ifwidth != 8)
+ return -EINVAL;
+
+ priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
+ LCD_FLAG_C | LCD_FLAG_B;
+
+ long_sleep(20); /* wait 20 ms after power-up for the paranoid */
+
+ /*
+ * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
+ * the LCD is in 8-bit mode afterwards
+ */
+ init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
+ if (lcd->ifwidth == 4) {
+ init >>= 4;
+ write_cmd_raw = lcd->ops->write_cmd_raw4;
+ } else {
+ write_cmd_raw = lcd->ops->write_cmd;
+ }
+ write_cmd_raw(lcd, init);
+ long_sleep(10);
+ write_cmd_raw(lcd, init);
+ long_sleep(10);
+ write_cmd_raw(lcd, init);
+ long_sleep(10);
+
+ if (lcd->ifwidth == 4) {
+ /* Switch to 4-bit mode, 1 line, small fonts */
+ lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4);
+ long_sleep(10);
+ }
+
+ /* set font height and lines number */
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_FUNCTION_SET |
+ ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
+ ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
+ ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
+ long_sleep(10);
+
+ /* display off, cursor off, blink off */
+ lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL);
+ long_sleep(10);
+
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_DISPLAY_CTRL | /* set display mode */
+ ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
+ ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
+ ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
+
+ charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
+
+ long_sleep(10);
+
+ /* entry mode set : increment, cursor shifting */
+ lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
+
+ charlcd_clear_display(lcd);
+ return 0;
+}
+
+/*
+ * These are the file operation function for user access to /dev/lcd
+ * This function can also be called from inside the kernel, by
+ * setting file and ppos to NULL.
+ *
+ */
+
+static inline int handle_lcd_special_code(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ /* LCD special codes */
+
+ int processed = 0;
+
+ char *esc = priv->esc_seq.buf + 2;
+ int oldflags = priv->flags;
+
+ /* check for display mode flags */
+ switch (*esc) {
+ case 'D': /* Display ON */
+ priv->flags |= LCD_FLAG_D;
+ processed = 1;
+ break;
+ case 'd': /* Display OFF */
+ priv->flags &= ~LCD_FLAG_D;
+ processed = 1;
+ break;
+ case 'C': /* Cursor ON */
+ priv->flags |= LCD_FLAG_C;
+ processed = 1;
+ break;
+ case 'c': /* Cursor OFF */
+ priv->flags &= ~LCD_FLAG_C;
+ processed = 1;
+ break;
+ case 'B': /* Blink ON */
+ priv->flags |= LCD_FLAG_B;
+ processed = 1;
+ break;
+ case 'b': /* Blink OFF */
+ priv->flags &= ~LCD_FLAG_B;
+ processed = 1;
+ break;
+ case '+': /* Back light ON */
+ priv->flags |= LCD_FLAG_L;
+ processed = 1;
+ break;
+ case '-': /* Back light OFF */
+ priv->flags &= ~LCD_FLAG_L;
+ processed = 1;
+ break;
+ case '*': /* Flash back light */
+ charlcd_poke(lcd);
+ processed = 1;
+ break;
+ case 'f': /* Small Font */
+ priv->flags &= ~LCD_FLAG_F;
+ processed = 1;
+ break;
+ case 'F': /* Large Font */
+ priv->flags |= LCD_FLAG_F;
+ processed = 1;
+ break;
+ case 'n': /* One Line */
+ priv->flags &= ~LCD_FLAG_N;
+ processed = 1;
+ break;
+ case 'N': /* Two Lines */
+ priv->flags |= LCD_FLAG_N;
+ break;
+ case 'l': /* Shift Cursor Left */
+ if (priv->addr.x > 0) {
+ /* back one char if not at end of line */
+ if (priv->addr.x < lcd->bwidth)
+ lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
+ priv->addr.x--;
+ }
+ processed = 1;
+ break;
+ case 'r': /* shift cursor right */
+ if (priv->addr.x < lcd->width) {
+ /* allow the cursor to pass the end of the line */
+ if (priv->addr.x < (lcd->bwidth - 1))
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
+ priv->addr.x++;
+ }
+ processed = 1;
+ break;
+ case 'L': /* shift display left */
+ lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
+ processed = 1;
+ break;
+ case 'R': /* shift display right */
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
+ LCD_CMD_SHIFT_RIGHT);
+ processed = 1;
+ break;
+ case 'k': { /* kill end of line */
+ int x;
+
+ for (x = priv->addr.x; x < lcd->bwidth; x++)
+ lcd->ops->write_data(lcd, ' ');
+
+ /* restore cursor position */
+ charlcd_gotoxy(lcd);
+ processed = 1;
+ break;
+ }
+ case 'I': /* reinitialize display */
+ charlcd_init_display(lcd);
+ processed = 1;
+ break;
+ case 'G': {
+ /* Generator : LGcxxxxx...xx; must have <c> between '0'
+ * and '7', representing the numerical ASCII code of the
+ * redefined character, and <xx...xx> a sequence of 16
+ * hex digits representing 8 bytes for each character.
+ * Most LCDs will only use 5 lower bits of the 7 first
+ * bytes.
+ */
+
+ unsigned char cgbytes[8];
+ unsigned char cgaddr;
+ int cgoffset;
+ int shift;
+ char value;
+ int addr;
+
+ if (!strchr(esc, ';'))
+ break;
+
+ esc++;
+
+ cgaddr = *(esc++) - '0';
+ if (cgaddr > 7) {
+ processed = 1;
+ break;
+ }
+
+ cgoffset = 0;
+ shift = 0;
+ value = 0;
+ while (*esc && cgoffset < 8) {
+ shift ^= 4;
+ if (*esc >= '0' && *esc <= '9') {
+ value |= (*esc - '0') << shift;
+ } else if (*esc >= 'A' && *esc <= 'Z') {
+ value |= (*esc - 'A' + 10) << shift;
+ } else if (*esc >= 'a' && *esc <= 'z') {
+ value |= (*esc - 'a' + 10) << shift;
+ } else {
+ esc++;
+ continue;
+ }
+
+ if (shift == 0) {
+ cgbytes[cgoffset++] = value;
+ value = 0;
+ }
+
+ esc++;
+ }
+
+ lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
+ for (addr = 0; addr < cgoffset; addr++)
+ lcd->ops->write_data(lcd, cgbytes[addr]);
+
+ /* ensures that we stop writing to CGRAM */
+ charlcd_gotoxy(lcd);
+ processed = 1;
+ break;
+ }
+ case 'x': /* gotoxy : LxXXX[yYYY]; */
+ case 'y': /* gotoxy : LyYYY[xXXX]; */
+ if (!strchr(esc, ';'))
+ break;
+
+ while (*esc) {
+ if (*esc == 'x') {
+ esc++;
+ if (kstrtoul(esc, 10, &priv->addr.x) < 0)
+ break;
+ } else if (*esc == 'y') {
+ esc++;
+ if (kstrtoul(esc, 10, &priv->addr.y) < 0)
+ break;
+ } else {
+ break;
+ }
+ }
+
+ charlcd_gotoxy(lcd);
+ processed = 1;
+ break;
+ }
+
+ /* TODO: This indent party here got ugly, clean it! */
+ /* Check whether one flag was changed */
+ if (oldflags == priv->flags)
+ return processed;
+
+ /* check whether one of B,C,D flags were changed */
+ if ((oldflags ^ priv->flags) &
+ (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
+ /* set display mode */
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_DISPLAY_CTRL |
+ ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
+ ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
+ ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
+ /* check whether one of F,N flags was changed */
+ else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
+ lcd->ops->write_cmd(lcd,
+ LCD_CMD_FUNCTION_SET |
+ ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
+ ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
+ ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
+ /* check whether L flag was changed */
+ else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
+ charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
+
+ return processed;
+}
+
+static void charlcd_write_char(struct charlcd *lcd, char c)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ /* first, we'll test if we're in escape mode */
+ if ((c != '\n') && priv->esc_seq.len >= 0) {
+ /* yes, let's add this char to the buffer */
+ priv->esc_seq.buf[priv->esc_seq.len++] = c;
+ priv->esc_seq.buf[priv->esc_seq.len] = 0;
+ } else {
+ /* aborts any previous escape sequence */
+ priv->esc_seq.len = -1;
+
+ switch (c) {
+ case LCD_ESCAPE_CHAR:
+ /* start of an escape sequence */
+ priv->esc_seq.len = 0;
+ priv->esc_seq.buf[priv->esc_seq.len] = 0;
+ break;
+ case '\b':
+ /* go back one char and clear it */
+ if (priv->addr.x > 0) {
+ /*
+ * check if we're not at the
+ * end of the line
+ */
+ if (priv->addr.x < lcd->bwidth)
+ /* back one char */
+ lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
+ priv->addr.x--;
+ }
+ /* replace with a space */
+ lcd->ops->write_data(lcd, ' ');
+ /* back one char again */
+ lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
+ break;
+ case '\014':
+ /* quickly clear the display */
+ charlcd_clear_fast(lcd);
+ break;
+ case '\n':
+ /*
+ * flush the remainder of the current line and
+ * go to the beginning of the next line
+ */
+ for (; priv->addr.x < lcd->bwidth; priv->addr.x++)
+ lcd->ops->write_data(lcd, ' ');
+ priv->addr.x = 0;
+ priv->addr.y = (priv->addr.y + 1) % lcd->height;
+ charlcd_gotoxy(lcd);
+ break;
+ case '\r':
+ /* go to the beginning of the same line */
+ priv->addr.x = 0;
+ charlcd_gotoxy(lcd);
+ break;
+ case '\t':
+ /* print a space instead of the tab */
+ charlcd_print(lcd, ' ');
+ break;
+ default:
+ /* simply print this char */
+ charlcd_print(lcd, c);
+ break;
+ }
+ }
+
+ /*
+ * now we'll see if we're in an escape mode and if the current
+ * escape sequence can be understood.
+ */
+ if (priv->esc_seq.len >= 2) {
+ int processed = 0;
+
+ if (!strcmp(priv->esc_seq.buf, "[2J")) {
+ /* clear the display */
+ charlcd_clear_fast(lcd);
+ processed = 1;
+ } else if (!strcmp(priv->esc_seq.buf, "[H")) {
+ /* cursor to home */
+ charlcd_home(lcd);
+ processed = 1;
+ }
+ /* codes starting with ^[[L */
+ else if ((priv->esc_seq.len >= 3) &&
+ (priv->esc_seq.buf[0] == '[') &&
+ (priv->esc_seq.buf[1] == 'L')) {
+ processed = handle_lcd_special_code(lcd);
+ }
+
+ /* LCD special escape codes */
+ /*
+ * flush the escape sequence if it's been processed
+ * or if it is getting too long.
+ */
+ if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
+ priv->esc_seq.len = -1;
+ } /* escape codes */
+}
+
+static struct charlcd *the_charlcd;
+
+static ssize_t charlcd_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ const char __user *tmp = buf;
+ char c;
+
+ for (; count-- > 0; (*ppos)++, tmp++) {
+ if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
+ /*
+ * let's be a little nice with other processes
+ * that need some CPU
+ */
+ schedule();
+
+ if (get_user(c, tmp))
+ return -EFAULT;
+
+ charlcd_write_char(the_charlcd, c);
+ }
+
+ return tmp - buf;
+}
+
+static int charlcd_open(struct inode *inode, struct file *file)
+{
+ struct charlcd_priv *priv = to_priv(the_charlcd);
+
+ if (!atomic_dec_and_test(&charlcd_available))
+ return -EBUSY; /* open only once at a time */
+
+ if (file->f_mode & FMODE_READ) /* device is write-only */
+ return -EPERM;
+
+ if (priv->must_clear) {
+ charlcd_clear_display(&priv->lcd);
+ priv->must_clear = false;
+ }
+ return nonseekable_open(inode, file);
+}
+
+static int charlcd_release(struct inode *inode, struct file *file)
+{
+ atomic_inc(&charlcd_available);
+ return 0;
+}
+
+static const struct file_operations charlcd_fops = {
+ .write = charlcd_write,
+ .open = charlcd_open,
+ .release = charlcd_release,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice charlcd_dev = {
+ .minor = LCD_MINOR,
+ .name = "lcd",
+ .fops = &charlcd_fops,
+};
+
+static void charlcd_puts(struct charlcd *lcd, const char *s)
+{
+ const char *tmp = s;
+ int count = strlen(s);
+
+ for (; count-- > 0; tmp++) {
+ if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
+ /*
+ * let's be a little nice with other processes
+ * that need some CPU
+ */
+ schedule();
+
+ charlcd_write_char(lcd, *tmp);
+ }
+}
+
+/* initialize the LCD driver */
+static int charlcd_init(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+ int ret;
+
+ if (lcd->ops->backlight) {
+ mutex_init(&priv->bl_tempo_lock);
+ INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
+ }
+
+ /*
+ * before this line, we must NOT send anything to the display.
+ * Since charlcd_init_display() needs to write data, we have to
+ * enable mark the LCD initialized just before.
+ */
+ ret = charlcd_init_display(lcd);
+ if (ret)
+ return ret;
+
+ /* display a short message */
+#ifdef CONFIG_PANEL_CHANGE_MESSAGE
+#ifdef CONFIG_PANEL_BOOT_MESSAGE
+ charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
+#endif
+#else
+ charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n");
+#endif
+ /* clear the display on the next device opening */
+ priv->must_clear = true;
+ charlcd_home(lcd);
+ return 0;
+}
+
+struct charlcd *charlcd_alloc(unsigned int drvdata_size)
+{
+ struct charlcd_priv *priv;
+ struct charlcd *lcd;
+
+ priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL);
+ if (!priv)
+ return NULL;
+
+ priv->esc_seq.len = -1;
+
+ lcd = &priv->lcd;
+ lcd->ifwidth = 8;
+ lcd->bwidth = DEFAULT_LCD_BWIDTH;
+ lcd->hwidth = DEFAULT_LCD_HWIDTH;
+ lcd->drvdata = priv->drvdata;
+
+ return lcd;
+}
+EXPORT_SYMBOL_GPL(charlcd_alloc);
+
+static int panel_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ struct charlcd *lcd = the_charlcd;
+
+ switch (code) {
+ case SYS_DOWN:
+ charlcd_puts(lcd,
+ "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
+ break;
+ case SYS_HALT:
+ charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
+ break;
+ case SYS_POWER_OFF:
+ charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
+ break;
+ default:
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panel_notifier = {
+ panel_notify_sys,
+ NULL,
+ 0
+};
+
+int charlcd_register(struct charlcd *lcd)
+{
+ int ret;
+
+ ret = charlcd_init(lcd);
+ if (ret)
+ return ret;
+
+ ret = misc_register(&charlcd_dev);
+ if (ret)
+ return ret;
+
+ the_charlcd = lcd;
+ register_reboot_notifier(&panel_notifier);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(charlcd_register);
+
+int charlcd_unregister(struct charlcd *lcd)
+{
+ struct charlcd_priv *priv = to_priv(lcd);
+
+ unregister_reboot_notifier(&panel_notifier);
+ charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
+ misc_deregister(&charlcd_dev);
+ the_charlcd = NULL;
+ if (lcd->ops->backlight) {
+ cancel_delayed_work_sync(&priv->bl_work);
+ priv->lcd.ops->backlight(&priv->lcd, 0);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(charlcd_unregister);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c
new file mode 100644
index 000000000000..036eec404289
--- /dev/null
+++ b/drivers/auxdisplay/hd44780.c
@@ -0,0 +1,326 @@
+/*
+ * HD44780 Character LCD driver for Linux
+ *
+ * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
+ * Copyright (C) 2016-2017 Glider bvba
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <misc/charlcd.h>
+
+
+enum hd44780_pin {
+ /* Order does matter due to writing to GPIO array subsets! */
+ PIN_DATA0, /* Optional */
+ PIN_DATA1, /* Optional */
+ PIN_DATA2, /* Optional */
+ PIN_DATA3, /* Optional */
+ PIN_DATA4,
+ PIN_DATA5,
+ PIN_DATA6,
+ PIN_DATA7,
+ PIN_CTRL_RS,
+ PIN_CTRL_RW, /* Optional */
+ PIN_CTRL_E,
+ PIN_CTRL_BL, /* Optional */
+ PIN_NUM
+};
+
+struct hd44780 {
+ struct gpio_desc *pins[PIN_NUM];
+};
+
+static void hd44780_backlight(struct charlcd *lcd, int on)
+{
+ struct hd44780 *hd = lcd->drvdata;
+
+ if (hd->pins[PIN_CTRL_BL])
+ gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
+}
+
+static void hd44780_strobe_gpio(struct hd44780 *hd)
+{
+ /* Maintain the data during 20 us before the strobe */
+ udelay(20);
+
+ gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1);
+
+ /* Maintain the strobe during 40 us */
+ udelay(40);
+
+ gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0);
+}
+
+/* write to an LCD panel register in 8 bit GPIO mode */
+static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs)
+{
+ int values[10]; /* for DATA[0-7], RS, RW */
+ unsigned int i, n;
+
+ for (i = 0; i < 8; i++)
+ values[PIN_DATA0 + i] = !!(val & BIT(i));
+ values[PIN_CTRL_RS] = rs;
+ n = 9;
+ if (hd->pins[PIN_CTRL_RW]) {
+ values[PIN_CTRL_RW] = 0;
+ n++;
+ }
+
+ /* Present the data to the port */
+ gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values);
+
+ hd44780_strobe_gpio(hd);
+}
+
+/* write to an LCD panel register in 4 bit GPIO mode */
+static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
+{
+ int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
+ unsigned int i, n;
+
+ /* High nibble + RS, RW */
+ for (i = 4; i < 8; i++)
+ values[PIN_DATA0 + i] = !!(val & BIT(i));
+ values[PIN_CTRL_RS] = rs;
+ n = 5;
+ if (hd->pins[PIN_CTRL_RW]) {
+ values[PIN_CTRL_RW] = 0;
+ n++;
+ }
+
+ /* Present the data to the port */
+ gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+ &values[PIN_DATA4]);
+
+ hd44780_strobe_gpio(hd);
+
+ /* Low nibble */
+ for (i = 0; i < 4; i++)
+ values[PIN_DATA4 + i] = !!(val & BIT(i));
+
+ /* Present the data to the port */
+ gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+ &values[PIN_DATA4]);
+
+ hd44780_strobe_gpio(hd);
+}
+
+/* Send a command to the LCD panel in 8 bit GPIO mode */
+static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
+{
+ struct hd44780 *hd = lcd->drvdata;
+
+ hd44780_write_gpio8(hd, cmd, 0);
+
+ /* The shortest command takes at least 120 us */
+ udelay(120);
+}
+
+/* Send data to the LCD panel in 8 bit GPIO mode */
+static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
+{
+ struct hd44780 *hd = lcd->drvdata;
+
+ hd44780_write_gpio8(hd, data, 1);
+
+ /* The shortest data takes at least 45 us */
+ udelay(45);
+}
+
+static const struct charlcd_ops hd44780_ops_gpio8 = {
+ .write_cmd = hd44780_write_cmd_gpio8,
+ .write_data = hd44780_write_data_gpio8,
+ .backlight = hd44780_backlight,
+};
+
+/* Send a command to the LCD panel in 4 bit GPIO mode */
+static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
+{
+ struct hd44780 *hd = lcd->drvdata;
+
+ hd44780_write_gpio4(hd, cmd, 0);
+
+ /* The shortest command takes at least 120 us */
+ udelay(120);
+}
+
+/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */
+static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd)
+{
+ int values[10]; /* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
+ struct hd44780 *hd = lcd->drvdata;
+ unsigned int i, n;
+
+ /* Command nibble + RS, RW */
+ for (i = 0; i < 4; i++)
+ values[PIN_DATA4 + i] = !!(cmd & BIT(i));
+ values[PIN_CTRL_RS] = 0;
+ n = 5;
+ if (hd->pins[PIN_CTRL_RW]) {
+ values[PIN_CTRL_RW] = 0;
+ n++;
+ }
+
+ /* Present the data to the port */
+ gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+ &values[PIN_DATA4]);
+
+ hd44780_strobe_gpio(hd);
+}
+
+/* Send data to the LCD panel in 4 bit GPIO mode */
+static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
+{
+ struct hd44780 *hd = lcd->drvdata;
+
+ hd44780_write_gpio4(hd, data, 1);
+
+ /* The shortest data takes at least 45 us */
+ udelay(45);
+}
+
+static const struct charlcd_ops hd44780_ops_gpio4 = {
+ .write_cmd = hd44780_write_cmd_gpio4,
+ .write_cmd_raw4 = hd44780_write_cmd_raw_gpio4,
+ .write_data = hd44780_write_data_gpio4,
+ .backlight = hd44780_backlight,
+};
+
+static int hd44780_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int i, base;
+ struct charlcd *lcd;
+ struct hd44780 *hd;
+ int ifwidth, ret;
+
+ /* Required pins */
+ ifwidth = gpiod_count(dev, "data");
+ if (ifwidth < 0)
+ return ifwidth;
+
+ switch (ifwidth) {
+ case 4:
+ base = PIN_DATA4;
+ break;
+ case 8:
+ base = PIN_DATA0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ lcd = charlcd_alloc(sizeof(struct hd44780));
+ if (!lcd)
+ return -ENOMEM;
+
+ hd = lcd->drvdata;
+
+ for (i = 0; i < ifwidth; i++) {
+ hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(hd->pins[base + i])) {
+ ret = PTR_ERR(hd->pins[base + i]);
+ goto fail;
+ }
+ }
+
+ hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(hd->pins[PIN_CTRL_E])) {
+ ret = PTR_ERR(hd->pins[PIN_CTRL_E]);
+ goto fail;
+ }
+
+ hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH);
+ if (IS_ERR(hd->pins[PIN_CTRL_RS])) {
+ ret = PTR_ERR(hd->pins[PIN_CTRL_RS]);
+ goto fail;
+ }
+
+ /* Optional pins */
+ hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
+ ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
+ goto fail;
+ }
+
+ hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(hd->pins[PIN_CTRL_BL])) {
+ ret = PTR_ERR(hd->pins[PIN_CTRL_BL]);
+ goto fail;
+ }
+
+ /* Required properties */
+ ret = device_property_read_u32(dev, "display-height-chars",
+ &lcd->height);
+ if (ret)
+ goto fail;
+ ret = device_property_read_u32(dev, "display-width-chars", &lcd->width);
+ if (ret)
+ goto fail;
+
+ /*
+ * On displays with more than two rows, the internal buffer width is
+ * usually equal to the display width
+ */
+ if (lcd->height > 2)
+ lcd->bwidth = lcd->width;
+
+ /* Optional properties */
+ device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth);
+
+ lcd->ifwidth = ifwidth;
+ lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
+
+ ret = charlcd_register(lcd);
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, lcd);
+ return 0;
+
+fail:
+ kfree(lcd);
+ return ret;
+}
+
+static int hd44780_remove(struct platform_device *pdev)
+{
+ struct charlcd *lcd = platform_get_drvdata(pdev);
+
+ charlcd_unregister(lcd);
+ return 0;
+}
+
+static const struct of_device_id hd44780_of_match[] = {
+ { .compatible = "hit,hd44780" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hd44780_of_match);
+
+static struct platform_driver hd44780_driver = {
+ .probe = hd44780_probe,
+ .remove = hd44780_remove,
+ .driver = {
+ .name = "hd44780",
+ .of_match_table = hd44780_of_match,
+ },
+};
+
+module_platform_driver(hd44780_driver);
+MODULE_DESCRIPTION("HD44780 Character LCD driver");
+MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c
index f66b45b235b0..fbfa5b4cc567 100644
--- a/drivers/auxdisplay/ht16k33.c
+++ b/drivers/auxdisplay/ht16k33.c
@@ -254,18 +254,22 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
{
const unsigned short *keycodes = keypad->dev->keycode;
u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS];
- u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2];
+ __le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS];
unsigned long bits_changed;
int row, col, code;
+ int rc;
bool pressed = false;
- if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) {
- dev_err(&keypad->client->dev, "Failed to read key data\n");
+ rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40,
+ sizeof(data), (u8 *)data);
+ if (rc != sizeof(data)) {
+ dev_err(&keypad->client->dev,
+ "Failed to read key data, rc=%d\n", rc);
return false;
}
for (col = 0; col < keypad->cols; col++) {
- new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2];
+ new_state[col] = le16_to_cpu(data[col]);
if (new_state[col])
pressed = true;
bits_changed = keypad->last_key_state[col] ^ new_state[col];
@@ -278,7 +282,7 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad)
}
}
input_sync(keypad->dev);
- memcpy(keypad->last_key_state, new_state, sizeof(new_state));
+ memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols);
return pressed;
}
@@ -353,6 +357,12 @@ static int ht16k33_keypad_probe(struct i2c_client *client,
err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols);
if (err)
return err;
+ if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS ||
+ cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) {
+ dev_err(&client->dev, "%u rows or %u cols out of range in DT\n",
+ rows, cols);
+ return -ERANGE;
+ }
keypad->rows = rows;
keypad->cols = cols;
diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c
index 83f1439e57fd..25306fa27251 100644
--- a/drivers/auxdisplay/img-ascii-lcd.c
+++ b/drivers/auxdisplay/img-ascii-lcd.c
@@ -220,6 +220,7 @@ static const struct of_device_id img_ascii_lcd_matches[] = {
{ .compatible = "mti,sead3-lcd", .data = &sead3_config },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches);
/**
* img_ascii_lcd_scroll() - scroll the display by a character
diff --git a/drivers/misc/panel.c b/drivers/auxdisplay/panel.c
index ef2ece0f26af..e0c014c2356f 100644
--- a/drivers/misc/panel.c
+++ b/drivers/auxdisplay/panel.c
@@ -1,6 +1,7 @@
/*
* Front panel driver for Linux
* Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
+ * Copyright (C) 2016-2017 Glider bvba
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -54,15 +55,12 @@
#include <linux/ctype.h>
#include <linux/parport.h>
#include <linux/list.h>
-#include <linux/notifier.h>
-#include <linux/reboot.h>
-#include <linux/workqueue.h>
-#include <generated/utsrelease.h>
#include <linux/io.h>
#include <linux/uaccess.h>
-#define LCD_MINOR 156
+#include <misc/charlcd.h>
+
#define KEYPAD_MINOR 185
#define LCD_MAXBYTES 256 /* max burst write */
@@ -76,9 +74,6 @@
/* a key repeats this times INPUT_POLL_TIME */
#define KEYPAD_REP_DELAY (2)
-/* keep the light on this many seconds for each flash */
-#define FLASH_LIGHT_TEMPO (4)
-
/* converts an r_str() input to an active high, bits string : 000BAOSE */
#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3)
@@ -120,40 +115,6 @@
#define PIN_SELECP 17
#define PIN_NOT_SET 127
-#define LCD_FLAG_B 0x0004 /* blink on */
-#define LCD_FLAG_C 0x0008 /* cursor on */
-#define LCD_FLAG_D 0x0010 /* display on */
-#define LCD_FLAG_F 0x0020 /* large font mode */
-#define LCD_FLAG_N 0x0040 /* 2-rows mode */
-#define LCD_FLAG_L 0x0080 /* backlight enabled */
-
-/* LCD commands */
-#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
-
-#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
-#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
-
-#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
-#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
-#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
-#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
-
-#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
-#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
-#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
-
-#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
-#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
-#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
-#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
-
-#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
-
-#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
-
-#define LCD_ESCAPE_LEN 24 /* max chars for LCD escape command */
-#define LCD_ESCAPE_CHAR 27 /* use char 27 for escape command */
-
#define NOT_SET -1
/* macros to simplify use of the parallel port */
@@ -245,19 +206,10 @@ static wait_queue_head_t keypad_read_wait;
static struct {
bool enabled;
bool initialized;
- bool must_clear;
- int height;
- int width;
- int bwidth;
- int hwidth;
int charset;
int proto;
- struct delayed_work bl_work;
- struct mutex bl_tempo_lock; /* Protects access to bl_tempo */
- bool bl_tempo;
-
/* TODO: use union here? */
struct {
int e;
@@ -268,20 +220,7 @@ static struct {
int bl;
} pins;
- /* contains the LCD config state */
- unsigned long int flags;
-
- /* Contains the LCD X and Y offset */
- struct {
- unsigned long int x;
- unsigned long int y;
- } addr;
-
- /* Current escape sequence and it's length or -1 if outside */
- struct {
- char buf[LCD_ESCAPE_LEN + 1];
- int len;
- } esc_seq;
+ struct charlcd *charlcd;
} lcd;
/* Needed only for init */
@@ -464,17 +403,12 @@ static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES];
/* global variables */
/* Device single-open policy control */
-static atomic_t lcd_available = ATOMIC_INIT(1);
static atomic_t keypad_available = ATOMIC_INIT(1);
static struct pardevice *pprt;
static int keypad_initialized;
-static void (*lcd_write_cmd)(int);
-static void (*lcd_write_data)(int);
-static void (*lcd_clear_fast)(void);
-
static DEFINE_SPINLOCK(pprt_lock);
static struct timer_list scan_timer;
@@ -574,8 +508,6 @@ static int keypad_enabled = NOT_SET;
module_param(keypad_enabled, int, 0000);
MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead");
-static const unsigned char *lcd_char_conv;
-
/* for some LCD drivers (ks0074) we need a charset conversion table. */
static const unsigned char lcd_char_conv_ks0074[256] = {
/* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */
@@ -752,15 +684,6 @@ static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val)
}
}
-/* sleeps that many milliseconds with a reschedule */
-static void long_sleep(int ms)
-{
- if (in_interrupt())
- mdelay(ms);
- else
- schedule_timeout_interruptible(msecs_to_jiffies(ms));
-}
-
/*
* send a serial byte to the LCD panel. The caller is responsible for locking
* if needed.
@@ -792,8 +715,11 @@ static void lcd_send_serial(int byte)
}
/* turn the backlight on or off */
-static void __lcd_backlight(int on)
+static void lcd_backlight(struct charlcd *charlcd, int on)
{
+ if (lcd.pins.bl == PIN_NONE)
+ return;
+
/* The backlight is activated by setting the AUTOFEED line to +5V */
spin_lock_irq(&pprt_lock);
if (on)
@@ -804,46 +730,8 @@ static void __lcd_backlight(int on)
spin_unlock_irq(&pprt_lock);
}
-static void lcd_backlight(int on)
-{
- if (lcd.pins.bl == PIN_NONE)
- return;
-
- mutex_lock(&lcd.bl_tempo_lock);
- if (!lcd.bl_tempo)
- __lcd_backlight(on);
- mutex_unlock(&lcd.bl_tempo_lock);
-}
-
-static void lcd_bl_off(struct work_struct *work)
-{
- mutex_lock(&lcd.bl_tempo_lock);
- if (lcd.bl_tempo) {
- lcd.bl_tempo = false;
- if (!(lcd.flags & LCD_FLAG_L))
- __lcd_backlight(0);
- }
- mutex_unlock(&lcd.bl_tempo_lock);
-}
-
-/* turn the backlight on for a little while */
-static void lcd_poke(void)
-{
- if (lcd.pins.bl == PIN_NONE)
- return;
-
- cancel_delayed_work_sync(&lcd.bl_work);
-
- mutex_lock(&lcd.bl_tempo_lock);
- if (!lcd.bl_tempo && !(lcd.flags & LCD_FLAG_L))
- __lcd_backlight(1);
- lcd.bl_tempo = true;
- schedule_delayed_work(&lcd.bl_work, FLASH_LIGHT_TEMPO * HZ);
- mutex_unlock(&lcd.bl_tempo_lock);
-}
-
/* send a command to the LCD panel in serial mode */
-static void lcd_write_cmd_s(int cmd)
+static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd)
{
spin_lock_irq(&pprt_lock);
lcd_send_serial(0x1F); /* R/W=W, RS=0 */
@@ -854,7 +742,7 @@ static void lcd_write_cmd_s(int cmd)
}
/* send data to the LCD panel in serial mode */
-static void lcd_write_data_s(int data)
+static void lcd_write_data_s(struct charlcd *charlcd, int data)
{
spin_lock_irq(&pprt_lock);
lcd_send_serial(0x5F); /* R/W=W, RS=1 */
@@ -865,7 +753,7 @@ static void lcd_write_data_s(int data)
}
/* send a command to the LCD panel in 8 bits parallel mode */
-static void lcd_write_cmd_p8(int cmd)
+static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -887,7 +775,7 @@ static void lcd_write_cmd_p8(int cmd)
}
/* send data to the LCD panel in 8 bits parallel mode */
-static void lcd_write_data_p8(int data)
+static void lcd_write_data_p8(struct charlcd *charlcd, int data)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -909,7 +797,7 @@ static void lcd_write_data_p8(int data)
}
/* send a command to the TI LCD panel */
-static void lcd_write_cmd_tilcd(int cmd)
+static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd)
{
spin_lock_irq(&pprt_lock);
/* present the data to the control port */
@@ -919,7 +807,7 @@ static void lcd_write_cmd_tilcd(int cmd)
}
/* send data to the TI LCD panel */
-static void lcd_write_data_tilcd(int data)
+static void lcd_write_data_tilcd(struct charlcd *charlcd, int data)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -928,47 +816,13 @@ static void lcd_write_data_tilcd(int data)
spin_unlock_irq(&pprt_lock);
}
-static void lcd_gotoxy(void)
-{
- lcd_write_cmd(LCD_CMD_SET_DDRAM_ADDR
- | (lcd.addr.y ? lcd.hwidth : 0)
- /*
- * we force the cursor to stay at the end of the
- * line if it wants to go farther
- */
- | ((lcd.addr.x < lcd.bwidth) ? lcd.addr.x &
- (lcd.hwidth - 1) : lcd.bwidth - 1));
-}
-
-static void lcd_home(void)
-{
- lcd.addr.x = 0;
- lcd.addr.y = 0;
- lcd_gotoxy();
-}
-
-static void lcd_print(char c)
-{
- if (lcd.addr.x < lcd.bwidth) {
- if (lcd_char_conv)
- c = lcd_char_conv[(unsigned char)c];
- lcd_write_data(c);
- lcd.addr.x++;
- }
- /* prevents the cursor from wrapping onto the next line */
- if (lcd.addr.x == lcd.bwidth)
- lcd_gotoxy();
-}
-
/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_s(void)
+static void lcd_clear_fast_s(struct charlcd *charlcd)
{
int pos;
- lcd_home();
-
spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
+ for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
lcd_send_serial(0x5F); /* R/W=W, RS=1 */
lcd_send_serial(' ' & 0x0F);
lcd_send_serial((' ' >> 4) & 0x0F);
@@ -976,19 +830,15 @@ static void lcd_clear_fast_s(void)
udelay(40);
}
spin_unlock_irq(&pprt_lock);
-
- lcd_home();
}
/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_p8(void)
+static void lcd_clear_fast_p8(struct charlcd *charlcd)
{
int pos;
- lcd_home();
-
spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
+ for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
/* present the data to the data port */
w_dtr(pprt, ' ');
@@ -1010,488 +860,62 @@ static void lcd_clear_fast_p8(void)
udelay(45);
}
spin_unlock_irq(&pprt_lock);
-
- lcd_home();
}
/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_tilcd(void)
+static void lcd_clear_fast_tilcd(struct charlcd *charlcd)
{
int pos;
- lcd_home();
-
spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < lcd.height * lcd.hwidth; pos++) {
+ for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
/* present the data to the data port */
w_dtr(pprt, ' ');
udelay(60);
}
spin_unlock_irq(&pprt_lock);
-
- lcd_home();
-}
-
-/* clears the display and resets X/Y */
-static void lcd_clear_display(void)
-{
- lcd_write_cmd(LCD_CMD_DISPLAY_CLEAR);
- lcd.addr.x = 0;
- lcd.addr.y = 0;
- /* we must wait a few milliseconds (15) */
- long_sleep(15);
}
-static void lcd_init_display(void)
-{
- lcd.flags = ((lcd.height > 1) ? LCD_FLAG_N : 0)
- | LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B;
-
- long_sleep(20); /* wait 20 ms after power-up for the paranoid */
-
- /* 8bits, 1 line, small fonts; let's do it 3 times */
- lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS);
- long_sleep(10);
- lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS);
- long_sleep(10);
- lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS);
- long_sleep(10);
-
- /* set font height and lines number */
- lcd_write_cmd(LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS
- | ((lcd.flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0)
- | ((lcd.flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)
- );
- long_sleep(10);
-
- /* display off, cursor off, blink off */
- lcd_write_cmd(LCD_CMD_DISPLAY_CTRL);
- long_sleep(10);
-
- lcd_write_cmd(LCD_CMD_DISPLAY_CTRL /* set display mode */
- | ((lcd.flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0)
- | ((lcd.flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0)
- | ((lcd.flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)
- );
-
- lcd_backlight((lcd.flags & LCD_FLAG_L) ? 1 : 0);
-
- long_sleep(10);
-
- /* entry mode set : increment, cursor shifting */
- lcd_write_cmd(LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
+static struct charlcd_ops charlcd_serial_ops = {
+ .write_cmd = lcd_write_cmd_s,
+ .write_data = lcd_write_data_s,
+ .clear_fast = lcd_clear_fast_s,
+ .backlight = lcd_backlight,
+};
- lcd_clear_display();
-}
+static struct charlcd_ops charlcd_parallel_ops = {
+ .write_cmd = lcd_write_cmd_p8,
+ .write_data = lcd_write_data_p8,
+ .clear_fast = lcd_clear_fast_p8,
+ .backlight = lcd_backlight,
+};
-/*
- * These are the file operation function for user access to /dev/lcd
- * This function can also be called from inside the kernel, by
- * setting file and ppos to NULL.
- *
- */
+static struct charlcd_ops charlcd_tilcd_ops = {
+ .write_cmd = lcd_write_cmd_tilcd,
+ .write_data = lcd_write_data_tilcd,
+ .clear_fast = lcd_clear_fast_tilcd,
+ .backlight = lcd_backlight,
+};
-static inline int handle_lcd_special_code(void)
+/* initialize the LCD driver */
+static void lcd_init(void)
{
- /* LCD special codes */
-
- int processed = 0;
+ struct charlcd *charlcd;
- char *esc = lcd.esc_seq.buf + 2;
- int oldflags = lcd.flags;
-
- /* check for display mode flags */
- switch (*esc) {
- case 'D': /* Display ON */
- lcd.flags |= LCD_FLAG_D;
- processed = 1;
- break;
- case 'd': /* Display OFF */
- lcd.flags &= ~LCD_FLAG_D;
- processed = 1;
- break;
- case 'C': /* Cursor ON */
- lcd.flags |= LCD_FLAG_C;
- processed = 1;
- break;
- case 'c': /* Cursor OFF */
- lcd.flags &= ~LCD_FLAG_C;
- processed = 1;
- break;
- case 'B': /* Blink ON */
- lcd.flags |= LCD_FLAG_B;
- processed = 1;
- break;
- case 'b': /* Blink OFF */
- lcd.flags &= ~LCD_FLAG_B;
- processed = 1;
- break;
- case '+': /* Back light ON */
- lcd.flags |= LCD_FLAG_L;
- processed = 1;
- break;
- case '-': /* Back light OFF */
- lcd.flags &= ~LCD_FLAG_L;
- processed = 1;
- break;
- case '*':
- /* flash back light */
- lcd_poke();
- processed = 1;
- break;
- case 'f': /* Small Font */
- lcd.flags &= ~LCD_FLAG_F;
- processed = 1;
- break;
- case 'F': /* Large Font */
- lcd.flags |= LCD_FLAG_F;
- processed = 1;
- break;
- case 'n': /* One Line */
- lcd.flags &= ~LCD_FLAG_N;
- processed = 1;
- break;
- case 'N': /* Two Lines */
- lcd.flags |= LCD_FLAG_N;
- break;
- case 'l': /* Shift Cursor Left */
- if (lcd.addr.x > 0) {
- /* back one char if not at end of line */
- if (lcd.addr.x < lcd.bwidth)
- lcd_write_cmd(LCD_CMD_SHIFT);
- lcd.addr.x--;
- }
- processed = 1;
- break;
- case 'r': /* shift cursor right */
- if (lcd.addr.x < lcd.width) {
- /* allow the cursor to pass the end of the line */
- if (lcd.addr.x < (lcd.bwidth - 1))
- lcd_write_cmd(LCD_CMD_SHIFT |
- LCD_CMD_SHIFT_RIGHT);
- lcd.addr.x++;
- }
- processed = 1;
- break;
- case 'L': /* shift display left */
- lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
- processed = 1;
- break;
- case 'R': /* shift display right */
- lcd_write_cmd(LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
- LCD_CMD_SHIFT_RIGHT);
- processed = 1;
- break;
- case 'k': { /* kill end of line */
- int x;
-
- for (x = lcd.addr.x; x < lcd.bwidth; x++)
- lcd_write_data(' ');
-
- /* restore cursor position */
- lcd_gotoxy();
- processed = 1;
- break;
- }
- case 'I': /* reinitialize display */
- lcd_init_display();
- processed = 1;
- break;
- case 'G': {
- /* Generator : LGcxxxxx...xx; must have <c> between '0'
- * and '7', representing the numerical ASCII code of the
- * redefined character, and <xx...xx> a sequence of 16
- * hex digits representing 8 bytes for each character.
- * Most LCDs will only use 5 lower bits of the 7 first
- * bytes.
- */
-
- unsigned char cgbytes[8];
- unsigned char cgaddr;
- int cgoffset;
- int shift;
- char value;
- int addr;
-
- if (!strchr(esc, ';'))
- break;
-
- esc++;
-
- cgaddr = *(esc++) - '0';
- if (cgaddr > 7) {
- processed = 1;
- break;
- }
-
- cgoffset = 0;
- shift = 0;
- value = 0;
- while (*esc && cgoffset < 8) {
- shift ^= 4;
- if (*esc >= '0' && *esc <= '9') {
- value |= (*esc - '0') << shift;
- } else if (*esc >= 'A' && *esc <= 'Z') {
- value |= (*esc - 'A' + 10) << shift;
- } else if (*esc >= 'a' && *esc <= 'z') {
- value |= (*esc - 'a' + 10) << shift;
- } else {
- esc++;
- continue;
- }
-
- if (shift == 0) {
- cgbytes[cgoffset++] = value;
- value = 0;
- }
-
- esc++;
- }
-
- lcd_write_cmd(LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
- for (addr = 0; addr < cgoffset; addr++)
- lcd_write_data(cgbytes[addr]);
-
- /* ensures that we stop writing to CGRAM */
- lcd_gotoxy();
- processed = 1;
- break;
- }
- case 'x': /* gotoxy : LxXXX[yYYY]; */
- case 'y': /* gotoxy : LyYYY[xXXX]; */
- if (!strchr(esc, ';'))
- break;
-
- while (*esc) {
- if (*esc == 'x') {
- esc++;
- if (kstrtoul(esc, 10, &lcd.addr.x) < 0)
- break;
- } else if (*esc == 'y') {
- esc++;
- if (kstrtoul(esc, 10, &lcd.addr.y) < 0)
- break;
- } else {
- break;
- }
- }
-
- lcd_gotoxy();
- processed = 1;
- break;
- }
-
- /* TODO: This indent party here got ugly, clean it! */
- /* Check whether one flag was changed */
- if (oldflags != lcd.flags) {
- /* check whether one of B,C,D flags were changed */
- if ((oldflags ^ lcd.flags) &
- (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
- /* set display mode */
- lcd_write_cmd(LCD_CMD_DISPLAY_CTRL
- | ((lcd.flags & LCD_FLAG_D)
- ? LCD_CMD_DISPLAY_ON : 0)
- | ((lcd.flags & LCD_FLAG_C)
- ? LCD_CMD_CURSOR_ON : 0)
- | ((lcd.flags & LCD_FLAG_B)
- ? LCD_CMD_BLINK_ON : 0));
- /* check whether one of F,N flags was changed */
- else if ((oldflags ^ lcd.flags) & (LCD_FLAG_F | LCD_FLAG_N))
- lcd_write_cmd(LCD_CMD_FUNCTION_SET
- | LCD_CMD_DATA_LEN_8BITS
- | ((lcd.flags & LCD_FLAG_F)
- ? LCD_CMD_FONT_5X10_DOTS
- : 0)
- | ((lcd.flags & LCD_FLAG_N)
- ? LCD_CMD_TWO_LINES
- : 0));
- /* check whether L flag was changed */
- else if ((oldflags ^ lcd.flags) & (LCD_FLAG_L))
- lcd_backlight(!!(lcd.flags & LCD_FLAG_L));
- }
-
- return processed;
-}
-
-static void lcd_write_char(char c)
-{
- /* first, we'll test if we're in escape mode */
- if ((c != '\n') && lcd.esc_seq.len >= 0) {
- /* yes, let's add this char to the buffer */
- lcd.esc_seq.buf[lcd.esc_seq.len++] = c;
- lcd.esc_seq.buf[lcd.esc_seq.len] = 0;
- } else {
- /* aborts any previous escape sequence */
- lcd.esc_seq.len = -1;
-
- switch (c) {
- case LCD_ESCAPE_CHAR:
- /* start of an escape sequence */
- lcd.esc_seq.len = 0;
- lcd.esc_seq.buf[lcd.esc_seq.len] = 0;
- break;
- case '\b':
- /* go back one char and clear it */
- if (lcd.addr.x > 0) {
- /*
- * check if we're not at the
- * end of the line
- */
- if (lcd.addr.x < lcd.bwidth)
- /* back one char */
- lcd_write_cmd(LCD_CMD_SHIFT);
- lcd.addr.x--;
- }
- /* replace with a space */
- lcd_write_data(' ');
- /* back one char again */
- lcd_write_cmd(LCD_CMD_SHIFT);
- break;
- case '\014':
- /* quickly clear the display */
- lcd_clear_fast();
- break;
- case '\n':
- /*
- * flush the remainder of the current line and
- * go to the beginning of the next line
- */
- for (; lcd.addr.x < lcd.bwidth; lcd.addr.x++)
- lcd_write_data(' ');
- lcd.addr.x = 0;
- lcd.addr.y = (lcd.addr.y + 1) % lcd.height;
- lcd_gotoxy();
- break;
- case '\r':
- /* go to the beginning of the same line */
- lcd.addr.x = 0;
- lcd_gotoxy();
- break;
- case '\t':
- /* print a space instead of the tab */
- lcd_print(' ');
- break;
- default:
- /* simply print this char */
- lcd_print(c);
- break;
- }
- }
+ charlcd = charlcd_alloc(0);
+ if (!charlcd)
+ return;
/*
- * now we'll see if we're in an escape mode and if the current
- * escape sequence can be understood.
+ * Init lcd struct with load-time values to preserve exact
+ * current functionality (at least for now).
*/
- if (lcd.esc_seq.len >= 2) {
- int processed = 0;
-
- if (!strcmp(lcd.esc_seq.buf, "[2J")) {
- /* clear the display */
- lcd_clear_fast();
- processed = 1;
- } else if (!strcmp(lcd.esc_seq.buf, "[H")) {
- /* cursor to home */
- lcd_home();
- processed = 1;
- }
- /* codes starting with ^[[L */
- else if ((lcd.esc_seq.len >= 3) &&
- (lcd.esc_seq.buf[0] == '[') &&
- (lcd.esc_seq.buf[1] == 'L')) {
- processed = handle_lcd_special_code();
- }
-
- /* LCD special escape codes */
- /*
- * flush the escape sequence if it's been processed
- * or if it is getting too long.
- */
- if (processed || (lcd.esc_seq.len >= LCD_ESCAPE_LEN))
- lcd.esc_seq.len = -1;
- } /* escape codes */
-}
+ charlcd->height = lcd_height;
+ charlcd->width = lcd_width;
+ charlcd->bwidth = lcd_bwidth;
+ charlcd->hwidth = lcd_hwidth;
-static ssize_t lcd_write(struct file *file,
- const char __user *buf, size_t count, loff_t *ppos)
-{
- const char __user *tmp = buf;
- char c;
-
- for (; count-- > 0; (*ppos)++, tmp++) {
- if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
- /*
- * let's be a little nice with other processes
- * that need some CPU
- */
- schedule();
-
- if (get_user(c, tmp))
- return -EFAULT;
-
- lcd_write_char(c);
- }
-
- return tmp - buf;
-}
-
-static int lcd_open(struct inode *inode, struct file *file)
-{
- if (!atomic_dec_and_test(&lcd_available))
- return -EBUSY; /* open only once at a time */
-
- if (file->f_mode & FMODE_READ) /* device is write-only */
- return -EPERM;
-
- if (lcd.must_clear) {
- lcd_clear_display();
- lcd.must_clear = false;
- }
- return nonseekable_open(inode, file);
-}
-
-static int lcd_release(struct inode *inode, struct file *file)
-{
- atomic_inc(&lcd_available);
- return 0;
-}
-
-static const struct file_operations lcd_fops = {
- .write = lcd_write,
- .open = lcd_open,
- .release = lcd_release,
- .llseek = no_llseek,
-};
-
-static struct miscdevice lcd_dev = {
- .minor = LCD_MINOR,
- .name = "lcd",
- .fops = &lcd_fops,
-};
-
-/* public function usable from the kernel for any purpose */
-static void panel_lcd_print(const char *s)
-{
- const char *tmp = s;
- int count = strlen(s);
-
- if (lcd.enabled && lcd.initialized) {
- for (; count-- > 0; tmp++) {
- if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
- /*
- * let's be a little nice with other processes
- * that need some CPU
- */
- schedule();
-
- lcd_write_char(*tmp);
- }
- }
-}
-
-/* initialize the LCD driver */
-static void lcd_init(void)
-{
switch (selected_lcd_type) {
case LCD_TYPE_OLD:
/* parallel mode, 8 bits */
@@ -1500,10 +924,10 @@ static void lcd_init(void)
lcd.pins.e = PIN_STROBE;
lcd.pins.rs = PIN_AUTOLF;
- lcd.width = 40;
- lcd.bwidth = 40;
- lcd.hwidth = 64;
- lcd.height = 2;
+ charlcd->width = 40;
+ charlcd->bwidth = 40;
+ charlcd->hwidth = 64;
+ charlcd->height = 2;
break;
case LCD_TYPE_KS0074:
/* serial mode, ks0074 */
@@ -1513,10 +937,10 @@ static void lcd_init(void)
lcd.pins.cl = PIN_STROBE;
lcd.pins.da = PIN_D0;
- lcd.width = 16;
- lcd.bwidth = 40;
- lcd.hwidth = 16;
- lcd.height = 2;
+ charlcd->width = 16;
+ charlcd->bwidth = 40;
+ charlcd->hwidth = 16;
+ charlcd->height = 2;
break;
case LCD_TYPE_NEXCOM:
/* parallel mode, 8 bits, generic */
@@ -1526,10 +950,10 @@ static void lcd_init(void)
lcd.pins.rs = PIN_SELECP;
lcd.pins.rw = PIN_INITP;
- lcd.width = 16;
- lcd.bwidth = 40;
- lcd.hwidth = 64;
- lcd.height = 2;
+ charlcd->width = 16;
+ charlcd->bwidth = 40;
+ charlcd->hwidth = 64;
+ charlcd->height = 2;
break;
case LCD_TYPE_CUSTOM:
/* customer-defined */
@@ -1545,22 +969,22 @@ static void lcd_init(void)
lcd.pins.e = PIN_STROBE;
lcd.pins.rs = PIN_SELECP;
- lcd.width = 16;
- lcd.bwidth = 40;
- lcd.hwidth = 64;
- lcd.height = 2;
+ charlcd->width = 16;
+ charlcd->bwidth = 40;
+ charlcd->hwidth = 64;
+ charlcd->height = 2;
break;
}
/* Overwrite with module params set on loading */
if (lcd_height != NOT_SET)
- lcd.height = lcd_height;
+ charlcd->height = lcd_height;
if (lcd_width != NOT_SET)
- lcd.width = lcd_width;
+ charlcd->width = lcd_width;
if (lcd_bwidth != NOT_SET)
- lcd.bwidth = lcd_bwidth;
+ charlcd->bwidth = lcd_bwidth;
if (lcd_hwidth != NOT_SET)
- lcd.hwidth = lcd_hwidth;
+ charlcd->hwidth = lcd_hwidth;
if (lcd_charset != NOT_SET)
lcd.charset = lcd_charset;
if (lcd_proto != NOT_SET)
@@ -1579,19 +1003,17 @@ static void lcd_init(void)
lcd.pins.bl = lcd_bl_pin;
/* this is used to catch wrong and default values */
- if (lcd.width <= 0)
- lcd.width = DEFAULT_LCD_WIDTH;
- if (lcd.bwidth <= 0)
- lcd.bwidth = DEFAULT_LCD_BWIDTH;
- if (lcd.hwidth <= 0)
- lcd.hwidth = DEFAULT_LCD_HWIDTH;
- if (lcd.height <= 0)
- lcd.height = DEFAULT_LCD_HEIGHT;
+ if (charlcd->width <= 0)
+ charlcd->width = DEFAULT_LCD_WIDTH;
+ if (charlcd->bwidth <= 0)
+ charlcd->bwidth = DEFAULT_LCD_BWIDTH;
+ if (charlcd->hwidth <= 0)
+ charlcd->hwidth = DEFAULT_LCD_HWIDTH;
+ if (charlcd->height <= 0)
+ charlcd->height = DEFAULT_LCD_HEIGHT;
if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */
- lcd_write_cmd = lcd_write_cmd_s;
- lcd_write_data = lcd_write_data_s;
- lcd_clear_fast = lcd_clear_fast_s;
+ charlcd->ops = &charlcd_serial_ops;
if (lcd.pins.cl == PIN_NOT_SET)
lcd.pins.cl = DEFAULT_LCD_PIN_SCL;
@@ -1599,9 +1021,7 @@ static void lcd_init(void)
lcd.pins.da = DEFAULT_LCD_PIN_SDA;
} else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */
- lcd_write_cmd = lcd_write_cmd_p8;
- lcd_write_data = lcd_write_data_p8;
- lcd_clear_fast = lcd_clear_fast_p8;
+ charlcd->ops = &charlcd_parallel_ops;
if (lcd.pins.e == PIN_NOT_SET)
lcd.pins.e = DEFAULT_LCD_PIN_E;
@@ -1610,9 +1030,7 @@ static void lcd_init(void)
if (lcd.pins.rw == PIN_NOT_SET)
lcd.pins.rw = DEFAULT_LCD_PIN_RW;
} else {
- lcd_write_cmd = lcd_write_cmd_tilcd;
- lcd_write_data = lcd_write_data_tilcd;
- lcd_clear_fast = lcd_clear_fast_tilcd;
+ charlcd->ops = &charlcd_tilcd_ops;
}
if (lcd.pins.bl == PIN_NOT_SET)
@@ -1635,14 +1053,9 @@ static void lcd_init(void)
lcd.charset = DEFAULT_LCD_CHARSET;
if (lcd.charset == LCD_CHARSET_KS0074)
- lcd_char_conv = lcd_char_conv_ks0074;
+ charlcd->char_conv = lcd_char_conv_ks0074;
else
- lcd_char_conv = NULL;
-
- if (lcd.pins.bl != PIN_NONE) {
- mutex_init(&lcd.bl_tempo_lock);
- INIT_DELAYED_WORK(&lcd.bl_work, lcd_bl_off);
- }
+ charlcd->char_conv = NULL;
pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E],
lcd_bits[LCD_PORT_C][LCD_BIT_E]);
@@ -1657,25 +1070,8 @@ static void lcd_init(void)
pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA],
lcd_bits[LCD_PORT_C][LCD_BIT_DA]);
- /*
- * before this line, we must NOT send anything to the display.
- * Since lcd_init_display() needs to write data, we have to
- * enable mark the LCD initialized just before.
- */
+ lcd.charlcd = charlcd;
lcd.initialized = true;
- lcd_init_display();
-
- /* display a short message */
-#ifdef CONFIG_PANEL_CHANGE_MESSAGE
-#ifdef CONFIG_PANEL_BOOT_MESSAGE
- panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE);
-#endif
-#else
- panel_lcd_print("\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE);
-#endif
- /* clear the display on the next device opening */
- lcd.must_clear = true;
- lcd_home();
}
/*
@@ -2011,7 +1407,7 @@ static void panel_scan_timer(void)
}
if (keypressed && lcd.enabled && lcd.initialized)
- lcd_poke();
+ charlcd_poke(lcd.charlcd);
mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME);
}
@@ -2175,35 +1571,6 @@ static void keypad_init(void)
/* device initialization */
/**************************************************/
-static int panel_notify_sys(struct notifier_block *this, unsigned long code,
- void *unused)
-{
- if (lcd.enabled && lcd.initialized) {
- switch (code) {
- case SYS_DOWN:
- panel_lcd_print
- ("\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- case SYS_HALT:
- panel_lcd_print
- ("\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- case SYS_POWER_OFF:
- panel_lcd_print("\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
- break;
- default:
- break;
- }
- }
- return NOTIFY_DONE;
-}
-
-static struct notifier_block panel_notifier = {
- panel_notify_sys,
- NULL,
- 0
-};
-
static void panel_attach(struct parport *port)
{
struct pardev_cb panel_cb;
@@ -2239,7 +1606,7 @@ static void panel_attach(struct parport *port)
*/
if (lcd.enabled) {
lcd_init();
- if (misc_register(&lcd_dev))
+ if (!lcd.charlcd || charlcd_register(lcd.charlcd))
goto err_unreg_device;
}
@@ -2248,13 +1615,14 @@ static void panel_attach(struct parport *port)
if (misc_register(&keypad_dev))
goto err_lcd_unreg;
}
- register_reboot_notifier(&panel_notifier);
return;
err_lcd_unreg:
if (lcd.enabled)
- misc_deregister(&lcd_dev);
+ charlcd_unregister(lcd.charlcd);
err_unreg_device:
+ kfree(lcd.charlcd);
+ lcd.charlcd = NULL;
parport_unregister_device(pprt);
pprt = NULL;
}
@@ -2278,20 +1646,16 @@ static void panel_detach(struct parport *port)
}
if (lcd.enabled) {
- panel_lcd_print("\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
- misc_deregister(&lcd_dev);
- if (lcd.pins.bl != PIN_NONE) {
- cancel_delayed_work_sync(&lcd.bl_work);
- __lcd_backlight(0);
- }
+ charlcd_unregister(lcd.charlcd);
lcd.initialized = false;
+ kfree(lcd.charlcd);
+ lcd.charlcd = NULL;
}
/* TODO: free all input signals */
parport_release(pprt);
parport_unregister_device(pprt);
pprt = NULL;
- unregister_reboot_notifier(&panel_notifier);
}
static struct parport_driver panel_driver = {
@@ -2369,10 +1733,6 @@ static int __init panel_init_module(void)
* Init lcd struct with load-time values to preserve exact
* current functionality (at least for now).
*/
- lcd.height = lcd_height;
- lcd.width = lcd_width;
- lcd.bwidth = lcd_bwidth;
- lcd.hwidth = lcd_hwidth;
lcd.charset = lcd_charset;
lcd.proto = lcd_proto;
lcd.pins.e = lcd_e_pin;
@@ -2381,9 +1741,6 @@ static int __init panel_init_module(void)
lcd.pins.cl = lcd_cl_pin;
lcd.pins.da = lcd_da_pin;
lcd.pins.bl = lcd_bl_pin;
-
- /* Leave it for now, just in case */
- lcd.esc_seq.len = -1;
}
switch (selected_keypad_type) {
diff --git a/drivers/char/hangcheck-timer.c b/drivers/char/hangcheck-timer.c
index 4f337375252e..5406b90bf626 100644
--- a/drivers/char/hangcheck-timer.c
+++ b/drivers/char/hangcheck-timer.c
@@ -32,7 +32,7 @@
* timer and 180 seconds for the margin of error. IOW, a timer is set
* for 60 seconds. When the timer fires, the callback checks the
* actual duration that the timer waited. If the duration exceeds the
- * alloted time and margin (here 60 + 180, or 240 seconds), the machine
+ * allotted time and margin (here 60 + 180, or 240 seconds), the machine
* is restarted. A healthy machine will have the duration match the
* expected timeout very closely.
*/
diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c
index 8bdc38d81adf..b941e6d59fd6 100644
--- a/drivers/char/hpet.c
+++ b/drivers/char/hpet.c
@@ -575,7 +575,7 @@ static inline unsigned long hpet_time_div(struct hpets *hpets,
}
static int
-hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg,
+hpet_ioctl_common(struct hpet_dev *devp, unsigned int cmd, unsigned long arg,
struct hpet_info *info)
{
struct hpet_timer __iomem *timer;
diff --git a/drivers/char/misc.c b/drivers/char/misc.c
index 8069b361b8dd..c9cd1ea6844a 100644
--- a/drivers/char/misc.c
+++ b/drivers/char/misc.c
@@ -109,7 +109,7 @@ static const struct file_operations misc_proc_fops = {
};
#endif
-static int misc_open(struct inode * inode, struct file * file)
+static int misc_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
struct miscdevice *c;
@@ -150,7 +150,7 @@ static int misc_open(struct inode * inode, struct file * file)
err = 0;
replace_fops(file, new_fops);
if (file->f_op->open)
- err = file->f_op->open(inode,file);
+ err = file->f_op->open(inode, file);
fail:
mutex_unlock(&misc_mtx);
return err;
@@ -182,7 +182,7 @@ static const struct file_operations misc_fops = {
* failure.
*/
-int misc_register(struct miscdevice * misc)
+int misc_register(struct miscdevice *misc)
{
dev_t dev;
int err = 0;
@@ -194,6 +194,7 @@ int misc_register(struct miscdevice * misc)
if (is_dynamic) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
+
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
goto out;
@@ -287,13 +288,13 @@ static int __init misc_init(void)
goto fail_remove;
err = -EIO;
- if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
+ if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
- printk("unable to get major %d for misc devices\n", MISC_MAJOR);
+ pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
if (ret)
diff --git a/drivers/char/mspec.c b/drivers/char/mspec.c
index a9c2fa3c81e5..7b75669d3670 100644
--- a/drivers/char/mspec.c
+++ b/drivers/char/mspec.c
@@ -43,6 +43,7 @@
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/numa.h>
+#include <linux/refcount.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <linux/atomic.h>
@@ -89,7 +90,7 @@ static int is_sn2;
* protect in fork case where multiple tasks share the vma_data.
*/
struct vma_data {
- atomic_t refcnt; /* Number of vmas sharing the data. */
+ refcount_t refcnt; /* Number of vmas sharing the data. */
spinlock_t lock; /* Serialize access to this structure. */
int count; /* Number of pages allocated. */
enum mspec_page_type type; /* Type of pages allocated. */
@@ -144,7 +145,7 @@ mspec_open(struct vm_area_struct *vma)
struct vma_data *vdata;
vdata = vma->vm_private_data;
- atomic_inc(&vdata->refcnt);
+ refcount_inc(&vdata->refcnt);
}
/*
@@ -162,7 +163,7 @@ mspec_close(struct vm_area_struct *vma)
vdata = vma->vm_private_data;
- if (!atomic_dec_and_test(&vdata->refcnt))
+ if (!refcount_dec_and_test(&vdata->refcnt))
return;
last_index = (vdata->vm_end - vdata->vm_start) >> PAGE_SHIFT;
@@ -274,7 +275,7 @@ mspec_mmap(struct file *file, struct vm_area_struct *vma,
vdata->vm_end = vma->vm_end;
vdata->type = type;
spin_lock_init(&vdata->lock);
- atomic_set(&vdata->refcnt, 1);
+ refcount_set(&vdata->refcnt, 1);
vma->vm_private_data = vdata;
vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 9dec9f551b83..322b8a51ffc6 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -218,8 +218,6 @@ struct tpm_chip *tpm_chip_alloc(struct device *pdev,
cdev_init(&chip->cdevs, &tpmrm_fops);
chip->cdev.owner = THIS_MODULE;
chip->cdevs.owner = THIS_MODULE;
- chip->cdev.kobj.parent = &chip->dev.kobj;
- chip->cdevs.kobj.parent = &chip->devs.kobj;
chip->work_space.context_buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!chip->work_space.context_buf) {
@@ -275,45 +273,24 @@ static int tpm_add_char_device(struct tpm_chip *chip)
{
int rc;
- rc = cdev_add(&chip->cdev, chip->dev.devt, 1);
+ rc = cdev_device_add(&chip->cdev, &chip->dev);
if (rc) {
dev_err(&chip->dev,
- "unable to cdev_add() %s, major %d, minor %d, err=%d\n",
+ "unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
dev_name(&chip->dev), MAJOR(chip->dev.devt),
MINOR(chip->dev.devt), rc);
return rc;
}
- rc = device_add(&chip->dev);
- if (rc) {
- dev_err(&chip->dev,
- "unable to device_register() %s, major %d, minor %d, err=%d\n",
- dev_name(&chip->dev), MAJOR(chip->dev.devt),
- MINOR(chip->dev.devt), rc);
-
- cdev_del(&chip->cdev);
- return rc;
- }
-
- if (chip->flags & TPM_CHIP_FLAG_TPM2)
- rc = cdev_add(&chip->cdevs, chip->devs.devt, 1);
- if (rc) {
- dev_err(&chip->dev,
- "unable to cdev_add() %s, major %d, minor %d, err=%d\n",
- dev_name(&chip->devs), MAJOR(chip->devs.devt),
- MINOR(chip->devs.devt), rc);
- return rc;
- }
-
- if (chip->flags & TPM_CHIP_FLAG_TPM2)
- rc = device_add(&chip->devs);
- if (rc) {
- dev_err(&chip->dev,
- "unable to device_register() %s, major %d, minor %d, err=%d\n",
- dev_name(&chip->devs), MAJOR(chip->devs.devt),
- MINOR(chip->devs.devt), rc);
- cdev_del(&chip->cdevs);
- return rc;
+ if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+ rc = cdev_device_add(&chip->cdevs, &chip->devs);
+ if (rc) {
+ dev_err(&chip->devs,
+ "unable to cdev_device_add() %s, major %d, minor %d, err=%d\n",
+ dev_name(&chip->devs), MAJOR(chip->devs.devt),
+ MINOR(chip->devs.devt), rc);
+ return rc;
+ }
}
/* Make the chip available. */
@@ -326,8 +303,7 @@ static int tpm_add_char_device(struct tpm_chip *chip)
static void tpm_del_char_device(struct tpm_chip *chip)
{
- cdev_del(&chip->cdev);
- device_del(&chip->dev);
+ cdev_device_del(&chip->cdev, &chip->dev);
/* Make the chip unavailable. */
mutex_lock(&idr_lock);
@@ -449,10 +425,8 @@ void tpm_chip_unregister(struct tpm_chip *chip)
{
tpm_del_legacy_sysfs(chip);
tpm_bios_log_teardown(chip);
- if (chip->flags & TPM_CHIP_FLAG_TPM2) {
- cdev_del(&chip->cdevs);
- device_del(&chip->devs);
- }
+ if (chip->flags & TPM_CHIP_FLAG_TPM2)
+ cdev_device_del(&chip->cdevs, &chip->devs);
tpm_del_char_device(chip);
}
EXPORT_SYMBOL_GPL(tpm_chip_unregister);
diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index 87fe111d0be6..7d041d026680 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -2304,7 +2304,7 @@ static int __init init(void)
pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL);
if (!pdrvdata.debugfs_dir)
- pr_warning("Error creating debugfs dir for virtio-ports\n");
+ pr_warn("Error creating debugfs dir for virtio-ports\n");
INIT_LIST_HEAD(&pdrvdata.consoles);
INIT_LIST_HEAD(&pdrvdata.portdevs);
diff --git a/drivers/dax/dax.c b/drivers/dax/dax.c
index 806f180c80d8..19795eb35579 100644
--- a/drivers/dax/dax.c
+++ b/drivers/dax/dax.c
@@ -703,13 +703,8 @@ static void dax_dev_release(struct device *dev)
kfree(dax_dev);
}
-static void unregister_dax_dev(void *dev)
+static void kill_dax_dev(struct dax_dev *dax_dev)
{
- struct dax_dev *dax_dev = to_dax_dev(dev);
- struct cdev *cdev = &dax_dev->cdev;
-
- dev_dbg(dev, "%s\n", __func__);
-
/*
* Note, rcu is not protecting the liveness of dax_dev, rcu is
* ensuring that any fault handlers that might have seen
@@ -720,8 +715,17 @@ static void unregister_dax_dev(void *dev)
dax_dev->alive = false;
synchronize_srcu(&dax_srcu);
unmap_mapping_range(dax_dev->inode->i_mapping, 0, 0, 1);
- cdev_del(cdev);
- device_unregister(dev);
+}
+
+static void unregister_dax_dev(void *dev)
+{
+ struct dax_dev *dax_dev = to_dax_dev(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ kill_dax_dev(dax_dev);
+ cdev_device_del(&dax_dev->cdev, dev);
+ put_device(dev);
}
struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
@@ -772,18 +776,13 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
goto err_inode;
}
- /* device_initialize() so cdev can reference kobj parent */
+ /* from here on we're committed to teardown via dax_dev_release() */
device_initialize(dev);
cdev = &dax_dev->cdev;
cdev_init(cdev, &dax_fops);
cdev->owner = parent->driver->owner;
- cdev->kobj.parent = &dev->kobj;
- rc = cdev_add(&dax_dev->cdev, dev_t, 1);
- if (rc)
- goto err_cdev;
- /* from here on we're committed to teardown via dax_dev_release() */
dax_dev->num_resources = count;
dax_dev->alive = true;
dax_dev->region = dax_region;
@@ -795,8 +794,10 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
dev->groups = dax_attribute_groups;
dev->release = dax_dev_release;
dev_set_name(dev, "dax%d.%d", dax_region->id, dax_dev->id);
- rc = device_add(dev);
+
+ rc = cdev_device_add(cdev, dev);
if (rc) {
+ kill_dax_dev(dax_dev);
put_device(dev);
return ERR_PTR(rc);
}
@@ -807,8 +808,6 @@ struct dax_dev *devm_create_dax_dev(struct dax_region *dax_region,
return dax_dev;
- err_cdev:
- iput(dax_dev->inode);
err_inode:
ida_simple_remove(&dax_minor_ida, minor);
err_minor:
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index fc09c76248b4..32f2dc8e4702 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -52,6 +52,13 @@ config EXTCON_INTEL_INT3496
This ACPI device is typically found on Intel Baytrail or Cherrytrail
based tablets, or other Baytrail / Cherrytrail devices.
+config EXTCON_INTEL_CHT_WC
+ tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
+ depends on INTEL_SOC_PMIC_CHTWC
+ help
+ Say Y here to enable extcon support for charger detection / control
+ on the Intel Cherrytrail Whiskey Cove PMIC.
+
config EXTCON_MAX14577
tristate "Maxim MAX14577/77836 EXTCON Support"
depends on MFD_MAX14577
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 237ac3f953c2..ecfa95804427 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
obj-$(CONFIG_EXTCON_AXP288) += extcon-axp288.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_INTEL_INT3496) += extcon-intel-int3496.o
+obj-$(CONFIG_EXTCON_INTEL_CHT_WC) += extcon-intel-cht-wc.o
obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
obj-$(CONFIG_EXTCON_MAX3355) += extcon-max3355.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c
index ed78b7c26627..e2d78cd7030d 100644
--- a/drivers/extcon/extcon-arizona.c
+++ b/drivers/extcon/extcon-arizona.c
@@ -51,6 +51,9 @@
#define HPDET_DEBOUNCE 500
#define DEFAULT_MICD_TIMEOUT 2000
+#define ARIZONA_HPDET_WAIT_COUNT 15
+#define ARIZONA_HPDET_WAIT_DELAY_MS 20
+
#define QUICK_HEADPHONE_MAX_OHM 3
#define MICROPHONE_MIN_OHM 1257
#define MICROPHONE_MAX_OHM 30000
@@ -1049,6 +1052,40 @@ static void arizona_hpdet_work(struct work_struct *work)
mutex_unlock(&info->lock);
}
+static int arizona_hpdet_wait(struct arizona_extcon_info *info)
+{
+ struct arizona *arizona = info->arizona;
+ unsigned int val;
+ int i, ret;
+
+ for (i = 0; i < ARIZONA_HPDET_WAIT_COUNT; i++) {
+ ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2,
+ &val);
+ if (ret) {
+ dev_err(arizona->dev,
+ "Failed to read HPDET state: %d\n", ret);
+ return ret;
+ }
+
+ switch (info->hpdet_ip_version) {
+ case 0:
+ if (val & ARIZONA_HP_DONE)
+ return 0;
+ break;
+ default:
+ if (val & ARIZONA_HP_DONE_B)
+ return 0;
+ break;
+ }
+
+ msleep(ARIZONA_HPDET_WAIT_DELAY_MS);
+ }
+
+ dev_warn(arizona->dev, "HPDET did not appear to complete\n");
+
+ return -ETIMEDOUT;
+}
+
static irqreturn_t arizona_jackdet(int irq, void *data)
{
struct arizona_extcon_info *info = data;
@@ -1155,6 +1192,15 @@ static irqreturn_t arizona_jackdet(int irq, void *data)
"Removal report failed: %d\n", ret);
}
+ /*
+ * If the jack was removed during a headphone detection we
+ * need to wait for the headphone detection to finish, as
+ * it can not be aborted. We don't want to be able to start
+ * a new headphone detection from a fresh insert until this
+ * one is finished.
+ */
+ arizona_hpdet_wait(info);
+
regmap_update_bits(arizona->regmap,
ARIZONA_JACK_DETECT_DEBOUNCE,
ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB,
diff --git a/drivers/extcon/extcon-intel-cht-wc.c b/drivers/extcon/extcon-intel-cht-wc.c
new file mode 100644
index 000000000000..91a0023074af
--- /dev/null
+++ b/drivers/extcon/extcon-intel-cht-wc.c
@@ -0,0 +1,395 @@
+/*
+ * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC
+ * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on various non upstream patches to support the CHT Whiskey Cove PMIC:
+ * Copyright (C) 2013-2015 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/extcon.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define CHT_WC_PHYCTRL 0x5e07
+
+#define CHT_WC_CHGRCTRL0 0x5e16
+#define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0)
+#define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1)
+#define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2)
+#define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3)
+#define CHT_WC_CHGRCTRL0_TTLCK_MASK BIT(4)
+#define CHT_WC_CHGRCTRL0_CCSM_OFF_MASK BIT(5)
+#define CHT_WC_CHGRCTRL0_DBPOFF_MASK BIT(6)
+#define CHT_WC_CHGRCTRL0_WDT_NOKICK BIT(7)
+
+#define CHT_WC_CHGRCTRL1 0x5e17
+
+#define CHT_WC_USBSRC 0x5e29
+#define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0)
+#define CHT_WC_USBSRC_STS_SUCCESS 2
+#define CHT_WC_USBSRC_STS_FAIL 3
+#define CHT_WC_USBSRC_TYPE_SHIFT 2
+#define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2)
+#define CHT_WC_USBSRC_TYPE_NONE 0
+#define CHT_WC_USBSRC_TYPE_SDP 1
+#define CHT_WC_USBSRC_TYPE_DCP 2
+#define CHT_WC_USBSRC_TYPE_CDP 3
+#define CHT_WC_USBSRC_TYPE_ACA 4
+#define CHT_WC_USBSRC_TYPE_SE1 5
+#define CHT_WC_USBSRC_TYPE_MHL 6
+#define CHT_WC_USBSRC_TYPE_FLOAT_DP_DN 7
+#define CHT_WC_USBSRC_TYPE_OTHER 8
+#define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9
+
+#define CHT_WC_PWRSRC_IRQ 0x6e03
+#define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f
+#define CHT_WC_PWRSRC_STS 0x6e1e
+#define CHT_WC_PWRSRC_VBUS BIT(0)
+#define CHT_WC_PWRSRC_DC BIT(1)
+#define CHT_WC_PWRSRC_BAT BIT(2)
+#define CHT_WC_PWRSRC_ID_GND BIT(3)
+#define CHT_WC_PWRSRC_ID_FLOAT BIT(4)
+
+#define CHT_WC_VBUS_GPIO_CTLO 0x6e2d
+#define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0)
+
+enum cht_wc_usb_id {
+ USB_ID_OTG,
+ USB_ID_GND,
+ USB_ID_FLOAT,
+ USB_RID_A,
+ USB_RID_B,
+ USB_RID_C,
+};
+
+enum cht_wc_mux_select {
+ MUX_SEL_PMIC = 0,
+ MUX_SEL_SOC,
+};
+
+static const unsigned int cht_wc_extcon_cables[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_CHG_USB_SDP,
+ EXTCON_CHG_USB_CDP,
+ EXTCON_CHG_USB_DCP,
+ EXTCON_CHG_USB_ACA,
+ EXTCON_NONE,
+};
+
+struct cht_wc_extcon_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct extcon_dev *edev;
+ unsigned int previous_cable;
+ bool usb_host;
+};
+
+static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
+{
+ if (pwrsrc_sts & CHT_WC_PWRSRC_ID_GND)
+ return USB_ID_GND;
+ if (pwrsrc_sts & CHT_WC_PWRSRC_ID_FLOAT)
+ return USB_ID_FLOAT;
+
+ /*
+ * Once we have iio support for the gpadc we should read the USBID
+ * gpadc channel here and determine ACA role based on that.
+ */
+ return USB_ID_FLOAT;
+}
+
+static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
+ bool ignore_errors)
+{
+ int ret, usbsrc, status;
+ unsigned long timeout;
+
+ /* Charger detection can take upto 600ms, wait 800ms max. */
+ timeout = jiffies + msecs_to_jiffies(800);
+ do {
+ ret = regmap_read(ext->regmap, CHT_WC_USBSRC, &usbsrc);
+ if (ret) {
+ dev_err(ext->dev, "Error reading usbsrc: %d\n", ret);
+ return ret;
+ }
+
+ status = usbsrc & CHT_WC_USBSRC_STS_MASK;
+ if (status == CHT_WC_USBSRC_STS_SUCCESS ||
+ status == CHT_WC_USBSRC_STS_FAIL)
+ break;
+
+ msleep(50); /* Wait a bit before retrying */
+ } while (time_before(jiffies, timeout));
+
+ if (status != CHT_WC_USBSRC_STS_SUCCESS) {
+ if (ignore_errors)
+ return EXTCON_CHG_USB_SDP; /* Save fallback */
+
+ if (status == CHT_WC_USBSRC_STS_FAIL)
+ dev_warn(ext->dev, "Could not detect charger type\n");
+ else
+ dev_warn(ext->dev, "Timeout detecting charger type\n");
+ return EXTCON_CHG_USB_SDP; /* Save fallback */
+ }
+
+ usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
+ switch (usbsrc) {
+ default:
+ dev_warn(ext->dev,
+ "Unhandled charger type %d, defaulting to SDP\n",
+ ret);
+ /* Fall through, treat as SDP */
+ case CHT_WC_USBSRC_TYPE_SDP:
+ case CHT_WC_USBSRC_TYPE_FLOAT_DP_DN:
+ case CHT_WC_USBSRC_TYPE_OTHER:
+ return EXTCON_CHG_USB_SDP;
+ case CHT_WC_USBSRC_TYPE_CDP:
+ return EXTCON_CHG_USB_CDP;
+ case CHT_WC_USBSRC_TYPE_DCP:
+ case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
+ case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
+ return EXTCON_CHG_USB_DCP;
+ case CHT_WC_USBSRC_TYPE_ACA:
+ return EXTCON_CHG_USB_ACA;
+ }
+}
+
+static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state)
+{
+ int ret;
+
+ ret = regmap_write(ext->regmap, CHT_WC_PHYCTRL, state);
+ if (ret)
+ dev_err(ext->dev, "Error writing phyctrl: %d\n", ret);
+}
+
+static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext,
+ bool enable)
+{
+ int ret, val;
+
+ val = enable ? CHT_WC_VBUS_GPIO_CTLO_OUTPUT : 0;
+
+ /*
+ * The 5V boost converter is enabled through a gpio on the PMIC, since
+ * there currently is no gpio driver we access the gpio reg directly.
+ */
+ ret = regmap_update_bits(ext->regmap, CHT_WC_VBUS_GPIO_CTLO,
+ CHT_WC_VBUS_GPIO_CTLO_OUTPUT, val);
+ if (ret)
+ dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n", ret);
+}
+
+/* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */
+static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext,
+ unsigned int cable, bool state)
+{
+ extcon_set_state_sync(ext->edev, cable, state);
+ if (cable == EXTCON_CHG_USB_SDP)
+ extcon_set_state_sync(ext->edev, EXTCON_USB, state);
+}
+
+static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
+{
+ int ret, pwrsrc_sts, id;
+ unsigned int cable = EXTCON_NONE;
+ /* Ignore errors in host mode, as the 5v boost converter is on then */
+ bool ignore_get_charger_errors = ext->usb_host;
+
+ ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
+ if (ret) {
+ dev_err(ext->dev, "Error reading pwrsrc status: %d\n", ret);
+ return;
+ }
+
+ id = cht_wc_extcon_get_id(ext, pwrsrc_sts);
+ if (id == USB_ID_GND) {
+ /* The 5v boost causes a false VBUS / SDP detect, skip */
+ goto charger_det_done;
+ }
+
+ /* Plugged into a host/charger or not connected? */
+ if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) {
+ /* Route D+ and D- to PMIC for future charger detection */
+ cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
+ goto set_state;
+ }
+
+ ret = cht_wc_extcon_get_charger(ext, ignore_get_charger_errors);
+ if (ret >= 0)
+ cable = ret;
+
+charger_det_done:
+ /* Route D+ and D- to SoC for the host or gadget controller */
+ cht_wc_extcon_set_phymux(ext, MUX_SEL_SOC);
+
+set_state:
+ if (cable != ext->previous_cable) {
+ cht_wc_extcon_set_state(ext, cable, true);
+ cht_wc_extcon_set_state(ext, ext->previous_cable, false);
+ ext->previous_cable = cable;
+ }
+
+ ext->usb_host = ((id == USB_ID_GND) || (id == USB_RID_A));
+ extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host);
+}
+
+static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
+{
+ struct cht_wc_extcon_data *ext = data;
+ int ret, irqs;
+
+ ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_IRQ, &irqs);
+ if (ret) {
+ dev_err(ext->dev, "Error reading irqs: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ cht_wc_extcon_pwrsrc_event(ext);
+
+ ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ, irqs);
+ if (ret) {
+ dev_err(ext->dev, "Error writing irqs: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
+{
+ int ret, mask, val;
+
+ mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF_MASK;
+ val = enable ? mask : 0;
+ ret = regmap_update_bits(ext->regmap, CHT_WC_CHGRCTRL0, mask, val);
+ if (ret)
+ dev_err(ext->dev, "Error setting sw control: %d\n", ret);
+
+ return ret;
+}
+
+static int cht_wc_extcon_probe(struct platform_device *pdev)
+{
+ struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+ struct cht_wc_extcon_data *ext;
+ int irq, ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ext = devm_kzalloc(&pdev->dev, sizeof(*ext), GFP_KERNEL);
+ if (!ext)
+ return -ENOMEM;
+
+ ext->dev = &pdev->dev;
+ ext->regmap = pmic->regmap;
+ ext->previous_cable = EXTCON_NONE;
+
+ /* Initialize extcon device */
+ ext->edev = devm_extcon_dev_allocate(ext->dev, cht_wc_extcon_cables);
+ if (IS_ERR(ext->edev))
+ return PTR_ERR(ext->edev);
+
+ /*
+ * When a host-cable is detected the BIOS enables an external 5v boost
+ * converter to power connected devices there are 2 problems with this:
+ * 1) This gets seen by the external battery charger as a valid Vbus
+ * supply and it then tries to feed Vsys from this creating a
+ * feedback loop which causes aprox. 300 mA extra battery drain
+ * (and unless we drive the external-charger-disable pin high it
+ * also tries to charge the battery causing even more feedback).
+ * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply
+ * Since the external battery charger has its own 5v boost converter
+ * which does not have these issues, we simply turn the separate
+ * external 5v boost converter off and leave it off entirely.
+ */
+ cht_wc_extcon_set_5v_boost(ext, false);
+
+ /* Enable sw control */
+ ret = cht_wc_extcon_sw_control(ext, true);
+ if (ret)
+ return ret;
+
+ /* Register extcon device */
+ ret = devm_extcon_dev_register(ext->dev, ext->edev);
+ if (ret) {
+ dev_err(ext->dev, "Error registering extcon device: %d\n", ret);
+ goto disable_sw_control;
+ }
+
+ /* Route D+ and D- to PMIC for initial charger detection */
+ cht_wc_extcon_set_phymux(ext, MUX_SEL_PMIC);
+
+ /* Get initial state */
+ cht_wc_extcon_pwrsrc_event(ext);
+
+ ret = devm_request_threaded_irq(ext->dev, irq, NULL, cht_wc_extcon_isr,
+ IRQF_ONESHOT, pdev->name, ext);
+ if (ret) {
+ dev_err(ext->dev, "Error requesting interrupt: %d\n", ret);
+ goto disable_sw_control;
+ }
+
+ /* Unmask irqs */
+ ret = regmap_write(ext->regmap, CHT_WC_PWRSRC_IRQ_MASK,
+ (int)~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_ID_GND |
+ CHT_WC_PWRSRC_ID_FLOAT));
+ if (ret) {
+ dev_err(ext->dev, "Error writing irq-mask: %d\n", ret);
+ goto disable_sw_control;
+ }
+
+ platform_set_drvdata(pdev, ext);
+
+ return 0;
+
+disable_sw_control:
+ cht_wc_extcon_sw_control(ext, false);
+ return ret;
+}
+
+static int cht_wc_extcon_remove(struct platform_device *pdev)
+{
+ struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev);
+
+ cht_wc_extcon_sw_control(ext, false);
+
+ return 0;
+}
+
+static const struct platform_device_id cht_wc_extcon_table[] = {
+ { .name = "cht_wcove_pwrsrc" },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table);
+
+static struct platform_driver cht_wc_extcon_driver = {
+ .probe = cht_wc_extcon_probe,
+ .remove = cht_wc_extcon_remove,
+ .id_table = cht_wc_extcon_table,
+ .driver = {
+ .name = "cht_wcove_pwrsrc",
+ },
+};
+module_platform_driver(cht_wc_extcon_driver);
+
+MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c
index ca904e8b3235..2af4369a2d73 100644
--- a/drivers/extcon/extcon-palmas.c
+++ b/drivers/extcon/extcon-palmas.c
@@ -413,6 +413,12 @@ static int palmas_usb_resume(struct device *dev)
if (palmas_usb->enable_gpio_id_detection)
disable_irq_wake(palmas_usb->gpio_id_irq);
}
+
+ /* check if GPIO states changed while suspend/resume */
+ if (palmas_usb->enable_gpio_vbus_detection)
+ palmas_vbus_irq_handler(palmas_usb->gpio_vbus_irq, palmas_usb);
+ palmas_gpio_id_detect(&palmas_usb->wq_detectid.work);
+
return 0;
};
#endif
diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c
index a5e1882b4ca6..9c925b05b7aa 100644
--- a/drivers/extcon/extcon-usb-gpio.c
+++ b/drivers/extcon/extcon-usb-gpio.c
@@ -26,7 +26,6 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
-#include <linux/acpi.h>
#include <linux/pinctrl/consumer.h>
#define USB_GPIO_DEBOUNCE_MS 20 /* ms */
@@ -111,7 +110,7 @@ static int usb_extcon_probe(struct platform_device *pdev)
struct usb_extcon_info *info;
int ret;
- if (!np && !ACPI_HANDLE(dev))
+ if (!np)
return -EINVAL;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
@@ -195,7 +194,7 @@ static int usb_extcon_probe(struct platform_device *pdev)
}
platform_set_drvdata(pdev, info);
- device_init_wakeup(dev, true);
+ device_set_wakeup_capable(&pdev->dev, true);
/* Perform initial detection */
usb_extcon_detect_cable(&info->wq_detcable.work);
@@ -282,9 +281,8 @@ static int usb_extcon_resume(struct device *dev)
if (info->vbus_gpiod)
enable_irq(info->vbus_irq);
- if (!device_may_wakeup(dev))
- queue_delayed_work(system_power_efficient_wq,
- &info->wq_detcable, 0);
+ queue_delayed_work(system_power_efficient_wq,
+ &info->wq_detcable, 0);
return ret;
}
diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c
index e7750545469f..f422a78ba342 100644
--- a/drivers/extcon/extcon.c
+++ b/drivers/extcon/extcon.c
@@ -230,9 +230,6 @@ struct extcon_cable {
};
static struct class *extcon_class;
-#if defined(CONFIG_ANDROID)
-static struct class_compat *switch_class;
-#endif /* CONFIG_ANDROID */
static LIST_HEAD(extcon_dev_list);
static DEFINE_MUTEX(extcon_dev_list_lock);
@@ -380,7 +377,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr,
for (i = 0; i < edev->max_supported; i++) {
count += sprintf(buf + count, "%s=%d\n",
extcon_info[edev->supported_cable[i]].name,
- !!(edev->state & (1 << i)));
+ !!(edev->state & BIT(i)));
}
return count;
@@ -1032,12 +1029,6 @@ static int create_extcon_class(void)
if (IS_ERR(extcon_class))
return PTR_ERR(extcon_class);
extcon_class->dev_groups = extcon_groups;
-
-#if defined(CONFIG_ANDROID)
- switch_class = class_compat_register("switch");
- if (WARN(!switch_class, "cannot allocate"))
- return -ENOMEM;
-#endif /* CONFIG_ANDROID */
}
return 0;
@@ -1259,10 +1250,6 @@ int extcon_dev_register(struct extcon_dev *edev)
put_device(&edev->dev);
goto err_dev;
}
-#if defined(CONFIG_ANDROID)
- if (switch_class)
- ret = class_compat_create_link(switch_class, &edev->dev, NULL);
-#endif /* CONFIG_ANDROID */
spin_lock_init(&edev->lock);
@@ -1350,10 +1337,6 @@ void extcon_dev_unregister(struct extcon_dev *edev)
kfree(edev->cables);
}
-#if defined(CONFIG_ANDROID)
- if (switch_class)
- class_compat_remove_link(switch_class, &edev->dev, NULL);
-#endif
put_device(&edev->dev);
}
EXPORT_SYMBOL_GPL(extcon_dev_unregister);
@@ -1424,9 +1407,6 @@ module_init(extcon_class_init);
static void __exit extcon_class_exit(void)
{
-#if defined(CONFIG_ANDROID)
- class_compat_unregister(switch_class);
-#endif
class_destroy(extcon_class);
}
module_exit(extcon_class_exit);
diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c
index 0de83508f321..939d259ddf19 100644
--- a/drivers/firewire/core-topology.c
+++ b/drivers/firewire/core-topology.c
@@ -124,7 +124,7 @@ static struct fw_node *fw_node_create(u32 sid, int port_count, int color)
node->initiated_reset = SELF_ID_PHY_INITIATOR(sid);
node->port_count = port_count;
- atomic_set(&node->ref_count, 1);
+ refcount_set(&node->ref_count, 1);
INIT_LIST_HEAD(&node->link);
return node;
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h
index e1480ff683d2..c07962ead5e4 100644
--- a/drivers/firewire/core.h
+++ b/drivers/firewire/core.h
@@ -12,7 +12,7 @@
#include <linux/slab.h>
#include <linux/types.h>
-#include <linux/atomic.h>
+#include <linux/refcount.h>
struct device;
struct fw_card;
@@ -184,7 +184,7 @@ struct fw_node {
* local node to this node. */
u8 max_depth:4; /* Maximum depth to any leaf node */
u8 max_hops:4; /* Max hops in this sub tree */
- atomic_t ref_count;
+ refcount_t ref_count;
/* For serializing node topology into a list. */
struct list_head link;
@@ -197,14 +197,14 @@ struct fw_node {
static inline struct fw_node *fw_node_get(struct fw_node *node)
{
- atomic_inc(&node->ref_count);
+ refcount_inc(&node->ref_count);
return node;
}
static inline void fw_node_put(struct fw_node *node)
{
- if (atomic_dec_and_test(&node->ref_count))
+ if (refcount_dec_and_test(&node->ref_count))
kfree(node);
}
diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/google/Kconfig
index 29c8cdda82a1..f16b381a569c 100644
--- a/drivers/firmware/google/Kconfig
+++ b/drivers/firmware/google/Kconfig
@@ -1,18 +1,16 @@
-config GOOGLE_FIRMWARE
+menuconfig GOOGLE_FIRMWARE
bool "Google Firmware Drivers"
- depends on X86
default n
help
These firmware drivers are used by Google's servers. They are
only useful if you are working directly on one of their
proprietary servers. If in doubt, say "N".
-menu "Google Firmware Drivers"
- depends on GOOGLE_FIRMWARE
+if GOOGLE_FIRMWARE
config GOOGLE_SMI
tristate "SMI interface for Google platforms"
- depends on ACPI && DMI && EFI
+ depends on X86 && ACPI && DMI && EFI
select EFI_VARS
help
Say Y here if you want to enable SMI callbacks for Google
@@ -20,12 +18,57 @@ config GOOGLE_SMI
clearing the EFI event log and reading and writing NVRAM
variables.
+config GOOGLE_COREBOOT_TABLE
+ tristate
+ depends on GOOGLE_COREBOOT_TABLE_ACPI || GOOGLE_COREBOOT_TABLE_OF
+
+config GOOGLE_COREBOOT_TABLE_ACPI
+ tristate "Coreboot Table Access - ACPI"
+ depends on ACPI
+ select GOOGLE_COREBOOT_TABLE
+ help
+ This option enables the coreboot_table module, which provides other
+ firmware modules to access to the coreboot table. The coreboot table
+ pointer is accessed through the ACPI "GOOGCB00" object.
+ If unsure say N.
+
+config GOOGLE_COREBOOT_TABLE_OF
+ tristate "Coreboot Table Access - Device Tree"
+ depends on OF
+ select GOOGLE_COREBOOT_TABLE
+ help
+ This option enable the coreboot_table module, which provide other
+ firmware modules to access coreboot table. The coreboot table pointer
+ is accessed through the device tree node /firmware/coreboot.
+ If unsure say N.
+
config GOOGLE_MEMCONSOLE
- tristate "Firmware Memory Console"
- depends on DMI
+ tristate
+ depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT
+
+config GOOGLE_MEMCONSOLE_X86_LEGACY
+ tristate "Firmware Memory Console - X86 Legacy support"
+ depends on X86 && ACPI && DMI
+ select GOOGLE_MEMCONSOLE
help
This option enables the kernel to search for a firmware log in
the EBDA on Google servers. If found, this log is exported to
userland in the file /sys/firmware/log.
-endmenu
+config GOOGLE_MEMCONSOLE_COREBOOT
+ tristate "Firmware Memory Console"
+ depends on GOOGLE_COREBOOT_TABLE
+ select GOOGLE_MEMCONSOLE
+ help
+ This option enables the kernel to search for a firmware log in
+ the coreboot table. If found, this log is exported to userland
+ in the file /sys/firmware/log.
+
+config GOOGLE_VPD
+ tristate "Vital Product Data"
+ depends on GOOGLE_COREBOOT_TABLE
+ help
+ This option enables the kernel to expose the content of Google VPD
+ under /sys/firmware/vpd.
+
+endif # GOOGLE_FIRMWARE
diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/google/Makefile
index 54a294e3cb61..bc4de02202ad 100644
--- a/drivers/firmware/google/Makefile
+++ b/drivers/firmware/google/Makefile
@@ -1,3 +1,11 @@
obj-$(CONFIG_GOOGLE_SMI) += gsmi.o
-obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_ACPI) += coreboot_table-acpi.o
+obj-$(CONFIG_GOOGLE_COREBOOT_TABLE_OF) += coreboot_table-of.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o
+obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o
+
+vpd-sysfs-y := vpd.o vpd_decode.o
+obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o
diff --git a/drivers/firmware/google/coreboot_table-acpi.c b/drivers/firmware/google/coreboot_table-acpi.c
new file mode 100644
index 000000000000..fb98db2d20e2
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table-acpi.c
@@ -0,0 +1,88 @@
+/*
+ * coreboot_table-acpi.c
+ *
+ * Using ACPI to locate Coreboot table and provide coreboot table access.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "coreboot_table.h"
+
+static int coreboot_table_acpi_probe(struct platform_device *pdev)
+{
+ phys_addr_t phyaddr;
+ resource_size_t len;
+ struct coreboot_table_header __iomem *header = NULL;
+ struct resource *res;
+ void __iomem *ptr = NULL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ len = resource_size(res);
+ if (!res->start || !len)
+ return -EINVAL;
+
+ phyaddr = res->start;
+ header = ioremap_cache(phyaddr, sizeof(*header));
+ if (header == NULL)
+ return -ENOMEM;
+
+ ptr = ioremap_cache(phyaddr,
+ header->header_bytes + header->table_bytes);
+ iounmap(header);
+ if (!ptr)
+ return -ENOMEM;
+
+ return coreboot_table_init(ptr);
+}
+
+static int coreboot_table_acpi_remove(struct platform_device *pdev)
+{
+ return coreboot_table_exit();
+}
+
+static const struct acpi_device_id cros_coreboot_acpi_match[] = {
+ { "GOOGCB00", 0 },
+ { "BOOT0000", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match);
+
+static struct platform_driver coreboot_table_acpi_driver = {
+ .probe = coreboot_table_acpi_probe,
+ .remove = coreboot_table_acpi_remove,
+ .driver = {
+ .name = "coreboot_table_acpi",
+ .acpi_match_table = ACPI_PTR(cros_coreboot_acpi_match),
+ },
+};
+
+static int __init coreboot_table_acpi_init(void)
+{
+ return platform_driver_register(&coreboot_table_acpi_driver);
+}
+
+module_init(coreboot_table_acpi_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table-of.c b/drivers/firmware/google/coreboot_table-of.c
new file mode 100644
index 000000000000..727acdc83e83
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table-of.c
@@ -0,0 +1,82 @@
+/*
+ * coreboot_table-of.c
+ *
+ * Coreboot table access through open firmware.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+#include "coreboot_table.h"
+
+static int coreboot_table_of_probe(struct platform_device *pdev)
+{
+ struct device_node *fw_dn = pdev->dev.of_node;
+ void __iomem *ptr;
+
+ ptr = of_iomap(fw_dn, 0);
+ of_node_put(fw_dn);
+ if (!ptr)
+ return -ENOMEM;
+
+ return coreboot_table_init(ptr);
+}
+
+static int coreboot_table_of_remove(struct platform_device *pdev)
+{
+ return coreboot_table_exit();
+}
+
+static const struct of_device_id coreboot_of_match[] = {
+ { .compatible = "coreboot" },
+ {},
+};
+
+static struct platform_driver coreboot_table_of_driver = {
+ .probe = coreboot_table_of_probe,
+ .remove = coreboot_table_of_remove,
+ .driver = {
+ .name = "coreboot_table_of",
+ .of_match_table = coreboot_of_match,
+ },
+};
+
+static int __init platform_coreboot_table_of_init(void)
+{
+ struct platform_device *pdev;
+ struct device_node *of_node;
+
+ /* Limit device creation to the presence of /firmware/coreboot node */
+ of_node = of_find_node_by_path("/firmware/coreboot");
+ if (!of_node)
+ return -ENODEV;
+
+ if (!of_match_node(coreboot_of_match, of_node))
+ return -ENODEV;
+
+ pdev = of_platform_device_create(of_node, "coreboot_table_of", NULL);
+ if (!pdev)
+ return -ENODEV;
+
+ return platform_driver_register(&coreboot_table_of_driver);
+}
+
+module_init(platform_coreboot_table_of_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/google/coreboot_table.c
new file mode 100644
index 000000000000..0019d3ec18dd
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table.c
@@ -0,0 +1,94 @@
+/*
+ * coreboot_table.c
+ *
+ * Module providing coreboot table access.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "coreboot_table.h"
+
+struct coreboot_table_entry {
+ u32 tag;
+ u32 size;
+};
+
+static struct coreboot_table_header __iomem *ptr_header;
+
+/*
+ * This function parses the coreboot table for an entry that contains the base
+ * address of the given entry tag. The coreboot table consists of a header
+ * directly followed by a number of small, variable-sized entries, which each
+ * contain an identifying tag and their length as the first two fields.
+ */
+int coreboot_table_find(int tag, void *data, size_t data_size)
+{
+ struct coreboot_table_header header;
+ struct coreboot_table_entry entry;
+ void *ptr_entry;
+ int i;
+
+ if (!ptr_header)
+ return -EPROBE_DEFER;
+
+ memcpy_fromio(&header, ptr_header, sizeof(header));
+
+ if (strncmp(header.signature, "LBIO", sizeof(header.signature))) {
+ pr_warn("coreboot_table: coreboot table missing or corrupt!\n");
+ return -ENODEV;
+ }
+
+ ptr_entry = (void *)ptr_header + header.header_bytes;
+
+ for (i = 0; i < header.table_entries; i++) {
+ memcpy_fromio(&entry, ptr_entry, sizeof(entry));
+ if (entry.tag == tag) {
+ if (data_size < entry.size)
+ return -EINVAL;
+
+ memcpy_fromio(data, ptr_entry, entry.size);
+
+ return 0;
+ }
+
+ ptr_entry += entry.size;
+ }
+
+ return -ENOENT;
+}
+EXPORT_SYMBOL(coreboot_table_find);
+
+int coreboot_table_init(void __iomem *ptr)
+{
+ ptr_header = ptr;
+
+ return 0;
+}
+EXPORT_SYMBOL(coreboot_table_init);
+
+int coreboot_table_exit(void)
+{
+ if (ptr_header)
+ iounmap(ptr_header);
+
+ return 0;
+}
+EXPORT_SYMBOL(coreboot_table_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/google/coreboot_table.h
new file mode 100644
index 000000000000..6eff1ae0c5d3
--- /dev/null
+++ b/drivers/firmware/google/coreboot_table.h
@@ -0,0 +1,50 @@
+/*
+ * coreboot_table.h
+ *
+ * Internal header for coreboot table access.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __COREBOOT_TABLE_H
+#define __COREBOOT_TABLE_H
+
+#include <linux/io.h>
+
+/* List of coreboot entry structures that is used */
+struct lb_cbmem_ref {
+ uint32_t tag;
+ uint32_t size;
+
+ uint64_t cbmem_addr;
+};
+
+/* Coreboot table header structure */
+struct coreboot_table_header {
+ char signature[4];
+ u32 header_bytes;
+ u32 header_checksum;
+ u32 table_bytes;
+ u32 table_checksum;
+ u32 table_entries;
+};
+
+/* Retrieve coreboot table entry with tag *tag* and copy it to data */
+int coreboot_table_find(int tag, void *data, size_t data_size);
+
+/* Initialize coreboot table module given a pointer to iomem */
+int coreboot_table_init(void __iomem *ptr);
+
+/* Cleanup coreboot table module */
+int coreboot_table_exit(void);
+
+#endif /* __COREBOOT_TABLE_H */
diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/google/memconsole-coreboot.c
new file mode 100644
index 000000000000..02711114dece
--- /dev/null
+++ b/drivers/firmware/google/memconsole-coreboot.c
@@ -0,0 +1,109 @@
+/*
+ * memconsole-coreboot.c
+ *
+ * Memory based BIOS console accessed through coreboot table.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "memconsole.h"
+#include "coreboot_table.h"
+
+#define CB_TAG_CBMEM_CONSOLE 0x17
+
+/* CBMEM firmware console log descriptor. */
+struct cbmem_cons {
+ u32 buffer_size;
+ u32 buffer_cursor;
+ u8 buffer_body[0];
+} __packed;
+
+static struct cbmem_cons __iomem *cbmem_console;
+
+static int memconsole_coreboot_init(phys_addr_t physaddr)
+{
+ struct cbmem_cons __iomem *tmp_cbmc;
+
+ tmp_cbmc = memremap(physaddr, sizeof(*tmp_cbmc), MEMREMAP_WB);
+
+ if (!tmp_cbmc)
+ return -ENOMEM;
+
+ cbmem_console = memremap(physaddr,
+ tmp_cbmc->buffer_size + sizeof(*cbmem_console),
+ MEMREMAP_WB);
+ memunmap(tmp_cbmc);
+
+ if (!cbmem_console)
+ return -ENOMEM;
+
+ memconsole_setup(cbmem_console->buffer_body,
+ min(cbmem_console->buffer_cursor, cbmem_console->buffer_size));
+
+ return 0;
+}
+
+static int memconsole_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct lb_cbmem_ref entry;
+
+ ret = coreboot_table_find(CB_TAG_CBMEM_CONSOLE, &entry, sizeof(entry));
+ if (ret)
+ return ret;
+
+ ret = memconsole_coreboot_init(entry.cbmem_addr);
+ if (ret)
+ return ret;
+
+ return memconsole_sysfs_init();
+}
+
+static int memconsole_remove(struct platform_device *pdev)
+{
+ memconsole_exit();
+
+ if (cbmem_console)
+ memunmap(cbmem_console);
+
+ return 0;
+}
+
+static struct platform_driver memconsole_driver = {
+ .probe = memconsole_probe,
+ .remove = memconsole_remove,
+ .driver = {
+ .name = "memconsole",
+ },
+};
+
+static int __init platform_memconsole_init(void)
+{
+ struct platform_device *pdev;
+
+ pdev = platform_device_register_simple("memconsole", -1, NULL, 0);
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ platform_driver_register(&memconsole_driver);
+
+ return 0;
+}
+
+module_init(platform_memconsole_init);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/google/memconsole-x86-legacy.c
new file mode 100644
index 000000000000..1f279ee883b9
--- /dev/null
+++ b/drivers/firmware/google/memconsole-x86-legacy.c
@@ -0,0 +1,153 @@
+/*
+ * memconsole-x86-legacy.c
+ *
+ * EBDA specific parts of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/mm.h>
+#include <asm/bios_ebda.h>
+#include <linux/acpi.h>
+
+#include "memconsole.h"
+
+#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
+#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
+
+struct biosmemcon_ebda {
+ u32 signature;
+ union {
+ struct {
+ u8 enabled;
+ u32 buffer_addr;
+ u16 start;
+ u16 end;
+ u16 num_chars;
+ u8 wrapped;
+ } __packed v1;
+ struct {
+ u32 buffer_addr;
+ /* Misdocumented as number of pages! */
+ u16 num_bytes;
+ u16 start;
+ u16 end;
+ } __packed v2;
+ };
+} __packed;
+
+static void found_v1_header(struct biosmemcon_ebda *hdr)
+{
+ pr_info("memconsole: BIOS console v1 EBDA structure found at %p\n",
+ hdr);
+ pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num = %d\n",
+ hdr->v1.buffer_addr, hdr->v1.start,
+ hdr->v1.end, hdr->v1.num_chars);
+
+ memconsole_setup(phys_to_virt(hdr->v1.buffer_addr), hdr->v1.num_chars);
+}
+
+static void found_v2_header(struct biosmemcon_ebda *hdr)
+{
+ pr_info("memconsole: BIOS console v2 EBDA structure found at %p\n",
+ hdr);
+ pr_info("memconsole: BIOS console buffer at 0x%.8x, start = %d, end = %d, num_bytes = %d\n",
+ hdr->v2.buffer_addr, hdr->v2.start,
+ hdr->v2.end, hdr->v2.num_bytes);
+
+ memconsole_setup(phys_to_virt(hdr->v2.buffer_addr + hdr->v2.start),
+ hdr->v2.end - hdr->v2.start);
+}
+
+/*
+ * Search through the EBDA for the BIOS Memory Console, and
+ * set the global variables to point to it. Return true if found.
+ */
+static bool memconsole_ebda_init(void)
+{
+ unsigned int address;
+ size_t length, cur;
+
+ address = get_bios_ebda();
+ if (!address) {
+ pr_info("memconsole: BIOS EBDA non-existent.\n");
+ return false;
+ }
+
+ /* EBDA length is byte 0 of EBDA (in KB) */
+ length = *(u8 *)phys_to_virt(address);
+ length <<= 10; /* convert to bytes */
+
+ /*
+ * Search through EBDA for BIOS memory console structure
+ * note: signature is not necessarily dword-aligned
+ */
+ for (cur = 0; cur < length; cur++) {
+ struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
+
+ /* memconsole v1 */
+ if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
+ found_v1_header(hdr);
+ return true;
+ }
+
+ /* memconsole v2 */
+ if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
+ found_v2_header(hdr);
+ return true;
+ }
+ }
+
+ pr_info("memconsole: BIOS console EBDA structure not found!\n");
+ return false;
+}
+
+static struct dmi_system_id memconsole_dmi_table[] __initdata = {
+ {
+ .ident = "Google Board",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
+ },
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
+
+static bool __init memconsole_find(void)
+{
+ if (!dmi_check_system(memconsole_dmi_table))
+ return false;
+
+ return memconsole_ebda_init();
+}
+
+static int __init memconsole_x86_init(void)
+{
+ if (!memconsole_find())
+ return -ENODEV;
+
+ return memconsole_sysfs_init();
+}
+
+static void __exit memconsole_x86_exit(void)
+{
+ memconsole_exit();
+}
+
+module_init(memconsole_x86_init);
+module_exit(memconsole_x86_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/google/memconsole.c
index 2f569aaed4c7..94e200ddb4fa 100644
--- a/drivers/firmware/google/memconsole.c
+++ b/drivers/firmware/google/memconsole.c
@@ -1,66 +1,36 @@
/*
* memconsole.c
*
- * Infrastructure for importing the BIOS memory based console
- * into the kernel log ringbuffer.
+ * Architecture-independent parts of the memory based BIOS console.
*
- * Copyright 2010 Google Inc. All rights reserved.
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*/
-#include <linux/ctype.h>
#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/module.h>
-#include <linux/dmi.h>
-#include <linux/io.h>
-#include <asm/bios_ebda.h>
-#define BIOS_MEMCONSOLE_V1_MAGIC 0xDEADBABE
-#define BIOS_MEMCONSOLE_V2_MAGIC (('M')|('C'<<8)|('O'<<16)|('N'<<24))
+#include "memconsole.h"
-struct biosmemcon_ebda {
- u32 signature;
- union {
- struct {
- u8 enabled;
- u32 buffer_addr;
- u16 start;
- u16 end;
- u16 num_chars;
- u8 wrapped;
- } __packed v1;
- struct {
- u32 buffer_addr;
- /* Misdocumented as number of pages! */
- u16 num_bytes;
- u16 start;
- u16 end;
- } __packed v2;
- };
-} __packed;
-
-static u32 memconsole_baseaddr;
+static char *memconsole_baseaddr;
static size_t memconsole_length;
static ssize_t memconsole_read(struct file *filp, struct kobject *kobp,
struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
- char *memconsole;
- ssize_t ret;
-
- memconsole = ioremap_cache(memconsole_baseaddr, memconsole_length);
- if (!memconsole) {
- pr_err("memconsole: ioremap_cache failed\n");
- return -ENOMEM;
- }
- ret = memory_read_from_buffer(buf, count, &pos, memconsole,
- memconsole_length);
- iounmap(memconsole);
- return ret;
+ return memory_read_from_buffer(buf, count, &pos, memconsole_baseaddr,
+ memconsole_length);
}
static struct bin_attribute memconsole_bin_attr = {
@@ -68,104 +38,25 @@ static struct bin_attribute memconsole_bin_attr = {
.read = memconsole_read,
};
-
-static void __init found_v1_header(struct biosmemcon_ebda *hdr)
-{
- pr_info("BIOS console v1 EBDA structure found at %p\n", hdr);
- pr_info("BIOS console buffer at 0x%.8x, "
- "start = %d, end = %d, num = %d\n",
- hdr->v1.buffer_addr, hdr->v1.start,
- hdr->v1.end, hdr->v1.num_chars);
-
- memconsole_length = hdr->v1.num_chars;
- memconsole_baseaddr = hdr->v1.buffer_addr;
-}
-
-static void __init found_v2_header(struct biosmemcon_ebda *hdr)
-{
- pr_info("BIOS console v2 EBDA structure found at %p\n", hdr);
- pr_info("BIOS console buffer at 0x%.8x, "
- "start = %d, end = %d, num_bytes = %d\n",
- hdr->v2.buffer_addr, hdr->v2.start,
- hdr->v2.end, hdr->v2.num_bytes);
-
- memconsole_length = hdr->v2.end - hdr->v2.start;
- memconsole_baseaddr = hdr->v2.buffer_addr + hdr->v2.start;
-}
-
-/*
- * Search through the EBDA for the BIOS Memory Console, and
- * set the global variables to point to it. Return true if found.
- */
-static bool __init found_memconsole(void)
+void memconsole_setup(void *baseaddr, size_t length)
{
- unsigned int address;
- size_t length, cur;
-
- address = get_bios_ebda();
- if (!address) {
- pr_info("BIOS EBDA non-existent.\n");
- return false;
- }
-
- /* EBDA length is byte 0 of EBDA (in KB) */
- length = *(u8 *)phys_to_virt(address);
- length <<= 10; /* convert to bytes */
-
- /*
- * Search through EBDA for BIOS memory console structure
- * note: signature is not necessarily dword-aligned
- */
- for (cur = 0; cur < length; cur++) {
- struct biosmemcon_ebda *hdr = phys_to_virt(address + cur);
-
- /* memconsole v1 */
- if (hdr->signature == BIOS_MEMCONSOLE_V1_MAGIC) {
- found_v1_header(hdr);
- return true;
- }
-
- /* memconsole v2 */
- if (hdr->signature == BIOS_MEMCONSOLE_V2_MAGIC) {
- found_v2_header(hdr);
- return true;
- }
- }
-
- pr_info("BIOS console EBDA structure not found!\n");
- return false;
+ memconsole_baseaddr = baseaddr;
+ memconsole_length = length;
}
+EXPORT_SYMBOL(memconsole_setup);
-static struct dmi_system_id memconsole_dmi_table[] __initdata = {
- {
- .ident = "Google Board",
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "Google, Inc."),
- },
- },
- {}
-};
-MODULE_DEVICE_TABLE(dmi, memconsole_dmi_table);
-
-static int __init memconsole_init(void)
+int memconsole_sysfs_init(void)
{
- if (!dmi_check_system(memconsole_dmi_table))
- return -ENODEV;
-
- if (!found_memconsole())
- return -ENODEV;
-
memconsole_bin_attr.size = memconsole_length;
return sysfs_create_bin_file(firmware_kobj, &memconsole_bin_attr);
}
+EXPORT_SYMBOL(memconsole_sysfs_init);
-static void __exit memconsole_exit(void)
+void memconsole_exit(void)
{
sysfs_remove_bin_file(firmware_kobj, &memconsole_bin_attr);
}
-
-module_init(memconsole_init);
-module_exit(memconsole_exit);
+EXPORT_SYMBOL(memconsole_exit);
MODULE_AUTHOR("Google, Inc.");
MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/google/memconsole.h
new file mode 100644
index 000000000000..190fc03a51ae
--- /dev/null
+++ b/drivers/firmware/google/memconsole.h
@@ -0,0 +1,43 @@
+/*
+ * memconsole.h
+ *
+ * Internal headers of the memory based BIOS console.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H
+#define __FIRMWARE_GOOGLE_MEMCONSOLE_H
+
+/*
+ * memconsole_setup
+ *
+ * Initialize the memory console from raw (virtual) base
+ * address and length.
+ */
+void memconsole_setup(void *baseaddr, size_t length);
+
+/*
+ * memconsole_sysfs_init
+ *
+ * Update memory console length and create binary file
+ * for firmware object.
+ */
+int memconsole_sysfs_init(void);
+
+/* memconsole_exit
+ *
+ * Unmap the console buffer.
+ */
+void memconsole_exit(void);
+
+#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */
diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c
new file mode 100644
index 000000000000..3ce813110d5e
--- /dev/null
+++ b/drivers/firmware/google/vpd.c
@@ -0,0 +1,332 @@
+/*
+ * vpd.c
+ *
+ * Driver for exporting VPD content to sysfs.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+
+#include "coreboot_table.h"
+#include "vpd_decode.h"
+
+#define CB_TAG_VPD 0x2c
+#define VPD_CBMEM_MAGIC 0x43524f53
+
+static struct kobject *vpd_kobj;
+
+struct vpd_cbmem {
+ u32 magic;
+ u32 version;
+ u32 ro_size;
+ u32 rw_size;
+ u8 blob[0];
+};
+
+struct vpd_section {
+ bool enabled;
+ const char *name;
+ char *raw_name; /* the string name_raw */
+ struct kobject *kobj; /* vpd/name directory */
+ char *baseaddr;
+ struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
+ struct list_head attribs; /* key/value in vpd_attrib_info list */
+};
+
+struct vpd_attrib_info {
+ char *key;
+ const char *value;
+ struct bin_attribute bin_attr;
+ struct list_head list;
+};
+
+static struct vpd_section ro_vpd;
+static struct vpd_section rw_vpd;
+
+static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct vpd_attrib_info *info = bin_attr->private;
+
+ return memory_read_from_buffer(buf, count, &pos, info->value,
+ info->bin_attr.size);
+}
+
+/*
+ * vpd_section_check_key_name()
+ *
+ * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
+ * old firmware versions may have entries like "S/N" which are problematic when
+ * exporting them as sysfs attributes. These keys present in old firmwares are
+ * ignored.
+ *
+ * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
+ *
+ * @key: The key name to check
+ * @key_len: key name length
+ */
+static int vpd_section_check_key_name(const u8 *key, s32 key_len)
+{
+ int c;
+
+ while (key_len-- > 0) {
+ c = *key++;
+
+ if (!isalnum(c) && c != '_')
+ return VPD_FAIL;
+ }
+
+ return VPD_OK;
+}
+
+static int vpd_section_attrib_add(const u8 *key, s32 key_len,
+ const u8 *value, s32 value_len,
+ void *arg)
+{
+ int ret;
+ struct vpd_section *sec = arg;
+ struct vpd_attrib_info *info;
+
+ /*
+ * Return VPD_OK immediately to decode next entry if the current key
+ * name contains invalid characters.
+ */
+ if (vpd_section_check_key_name(key, key_len) != VPD_OK)
+ return VPD_OK;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ info->key = kzalloc(key_len + 1, GFP_KERNEL);
+ if (!info->key)
+ return -ENOMEM;
+
+ memcpy(info->key, key, key_len);
+
+ sysfs_bin_attr_init(&info->bin_attr);
+ info->bin_attr.attr.name = info->key;
+ info->bin_attr.attr.mode = 0444;
+ info->bin_attr.size = value_len;
+ info->bin_attr.read = vpd_attrib_read;
+ info->bin_attr.private = info;
+
+ info->value = value;
+
+ INIT_LIST_HEAD(&info->list);
+ list_add_tail(&info->list, &sec->attribs);
+
+ ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
+ if (ret) {
+ kfree(info->key);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vpd_section_attrib_destroy(struct vpd_section *sec)
+{
+ struct vpd_attrib_info *info;
+ struct vpd_attrib_info *temp;
+
+ list_for_each_entry_safe(info, temp, &sec->attribs, list) {
+ kfree(info->key);
+ sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
+ kfree(info);
+ }
+}
+
+static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct vpd_section *sec = bin_attr->private;
+
+ return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
+ sec->bin_attr.size);
+}
+
+static int vpd_section_create_attribs(struct vpd_section *sec)
+{
+ s32 consumed;
+ int ret;
+
+ consumed = 0;
+ do {
+ ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
+ &consumed, vpd_section_attrib_add, sec);
+ } while (ret == VPD_OK);
+
+ return 0;
+}
+
+static int vpd_section_init(const char *name, struct vpd_section *sec,
+ phys_addr_t physaddr, size_t size)
+{
+ int ret;
+ int raw_len;
+
+ sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
+ if (!sec->baseaddr)
+ return -ENOMEM;
+
+ sec->name = name;
+
+ /* We want to export the raw partion with name ${name}_raw */
+ raw_len = strlen(name) + 5;
+ sec->raw_name = kzalloc(raw_len, GFP_KERNEL);
+ strncpy(sec->raw_name, name, raw_len);
+ strncat(sec->raw_name, "_raw", raw_len);
+
+ sysfs_bin_attr_init(&sec->bin_attr);
+ sec->bin_attr.attr.name = sec->raw_name;
+ sec->bin_attr.attr.mode = 0444;
+ sec->bin_attr.size = size;
+ sec->bin_attr.read = vpd_section_read;
+ sec->bin_attr.private = sec;
+
+ ret = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
+ if (ret)
+ goto free_sec;
+
+ sec->kobj = kobject_create_and_add(name, vpd_kobj);
+ if (!sec->kobj) {
+ ret = -EINVAL;
+ goto sysfs_remove;
+ }
+
+ INIT_LIST_HEAD(&sec->attribs);
+ vpd_section_create_attribs(sec);
+
+ sec->enabled = true;
+
+ return 0;
+
+sysfs_remove:
+ sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+
+free_sec:
+ kfree(sec->raw_name);
+ iounmap(sec->baseaddr);
+
+ return ret;
+}
+
+static int vpd_section_destroy(struct vpd_section *sec)
+{
+ if (sec->enabled) {
+ vpd_section_attrib_destroy(sec);
+ kobject_del(sec->kobj);
+ sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
+ kfree(sec->raw_name);
+ iounmap(sec->baseaddr);
+ }
+
+ return 0;
+}
+
+static int vpd_sections_init(phys_addr_t physaddr)
+{
+ struct vpd_cbmem __iomem *temp;
+ struct vpd_cbmem header;
+ int ret = 0;
+
+ temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
+ if (!temp)
+ return -ENOMEM;
+
+ memcpy_fromio(&header, temp, sizeof(struct vpd_cbmem));
+ iounmap(temp);
+
+ if (header.magic != VPD_CBMEM_MAGIC)
+ return -ENODEV;
+
+ if (header.ro_size) {
+ ret = vpd_section_init("ro", &ro_vpd,
+ physaddr + sizeof(struct vpd_cbmem),
+ header.ro_size);
+ if (ret)
+ return ret;
+ }
+
+ if (header.rw_size) {
+ ret = vpd_section_init("rw", &rw_vpd,
+ physaddr + sizeof(struct vpd_cbmem) +
+ header.ro_size, header.rw_size);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vpd_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct lb_cbmem_ref entry;
+
+ ret = coreboot_table_find(CB_TAG_VPD, &entry, sizeof(entry));
+ if (ret)
+ return ret;
+
+ return vpd_sections_init(entry.cbmem_addr);
+}
+
+static struct platform_driver vpd_driver = {
+ .probe = vpd_probe,
+ .driver = {
+ .name = "vpd",
+ },
+};
+
+static int __init vpd_platform_init(void)
+{
+ struct platform_device *pdev;
+
+ pdev = platform_device_register_simple("vpd", -1, NULL, 0);
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
+ if (!vpd_kobj)
+ return -ENOMEM;
+
+ memset(&ro_vpd, 0, sizeof(ro_vpd));
+ memset(&rw_vpd, 0, sizeof(rw_vpd));
+
+ platform_driver_register(&vpd_driver);
+
+ return 0;
+}
+
+static void __exit vpd_platform_exit(void)
+{
+ vpd_section_destroy(&ro_vpd);
+ vpd_section_destroy(&rw_vpd);
+ kobject_del(vpd_kobj);
+}
+
+module_init(vpd_platform_init);
+module_exit(vpd_platform_exit);
+
+MODULE_AUTHOR("Google, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/google/vpd_decode.c
new file mode 100644
index 000000000000..943acaa8aa76
--- /dev/null
+++ b/drivers/firmware/google/vpd_decode.c
@@ -0,0 +1,99 @@
+/*
+ * vpd_decode.c
+ *
+ * Google VPD decoding routines.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/export.h>
+
+#include "vpd_decode.h"
+
+static int vpd_decode_len(const s32 max_len, const u8 *in,
+ s32 *length, s32 *decoded_len)
+{
+ u8 more;
+ int i = 0;
+
+ if (!length || !decoded_len)
+ return VPD_FAIL;
+
+ *length = 0;
+ do {
+ if (i >= max_len)
+ return VPD_FAIL;
+
+ more = in[i] & 0x80;
+ *length <<= 7;
+ *length |= in[i] & 0x7f;
+ ++i;
+ } while (more);
+
+ *decoded_len = i;
+
+ return VPD_OK;
+}
+
+int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
+ vpd_decode_callback callback, void *callback_arg)
+{
+ int type;
+ int res;
+ s32 key_len;
+ s32 value_len;
+ s32 decoded_len;
+ const u8 *key;
+ const u8 *value;
+
+ /* type */
+ if (*consumed >= max_len)
+ return VPD_FAIL;
+
+ type = input_buf[*consumed];
+
+ switch (type) {
+ case VPD_TYPE_INFO:
+ case VPD_TYPE_STRING:
+ (*consumed)++;
+
+ /* key */
+ res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
+ &key_len, &decoded_len);
+ if (res != VPD_OK || *consumed + decoded_len >= max_len)
+ return VPD_FAIL;
+
+ *consumed += decoded_len;
+ key = &input_buf[*consumed];
+ *consumed += key_len;
+
+ /* value */
+ res = vpd_decode_len(max_len - *consumed, &input_buf[*consumed],
+ &value_len, &decoded_len);
+ if (res != VPD_OK || *consumed + decoded_len > max_len)
+ return VPD_FAIL;
+
+ *consumed += decoded_len;
+ value = &input_buf[*consumed];
+ *consumed += value_len;
+
+ if (type == VPD_TYPE_STRING)
+ return callback(key, key_len, value, value_len,
+ callback_arg);
+ break;
+
+ default:
+ return VPD_FAIL;
+ }
+
+ return VPD_OK;
+}
diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/google/vpd_decode.h
new file mode 100644
index 000000000000..be3d62c5ca2f
--- /dev/null
+++ b/drivers/firmware/google/vpd_decode.h
@@ -0,0 +1,58 @@
+/*
+ * vpd_decode.h
+ *
+ * Google VPD decoding routines.
+ *
+ * Copyright 2017 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License v2.0 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __VPD_DECODE_H
+#define __VPD_DECODE_H
+
+#include <linux/types.h>
+
+enum {
+ VPD_OK = 0,
+ VPD_FAIL,
+};
+
+enum {
+ VPD_TYPE_TERMINATOR = 0,
+ VPD_TYPE_STRING,
+ VPD_TYPE_INFO = 0xfe,
+ VPD_TYPE_IMPLICIT_TERMINATOR = 0xff,
+};
+
+/* Callback for vpd_decode_string to invoke. */
+typedef int vpd_decode_callback(const u8 *key, s32 key_len,
+ const u8 *value, s32 value_len,
+ void *arg);
+
+/*
+ * vpd_decode_string
+ *
+ * Given the encoded string, this function invokes callback with extracted
+ * (key, value). The *consumed will be plused the number of bytes consumed in
+ * this function.
+ *
+ * The input_buf points to the first byte of the input buffer.
+ *
+ * The *consumed starts from 0, which is actually the next byte to be decoded.
+ * It can be non-zero to be used in multiple calls.
+ *
+ * If one entry is successfully decoded, sends it to callback and returns the
+ * result.
+ */
+int vpd_decode_string(const s32 max_len, const u8 *input_buf, s32 *consumed,
+ vpd_decode_callback callback, void *callback_arg);
+
+#endif /* __VPD_DECODE_H */
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index ce861a2853a4..161ba9dccede 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -20,6 +20,12 @@ config FPGA_REGION
FPGA Regions allow loading FPGA images under control of
the Device Tree.
+config FPGA_MGR_ICE40_SPI
+ tristate "Lattice iCE40 SPI"
+ depends on OF && SPI
+ help
+ FPGA manager driver support for Lattice iCE40 FPGAs over SPI.
+
config FPGA_MGR_SOCFPGA
tristate "Altera SOCFPGA FPGA Manager"
depends on ARCH_SOCFPGA || COMPILE_TEST
@@ -33,6 +39,20 @@ config FPGA_MGR_SOCFPGA_A10
help
FPGA manager driver support for Altera Arria10 SoCFPGA.
+config FPGA_MGR_TS73XX
+ tristate "Technologic Systems TS-73xx SBC FPGA Manager"
+ depends on ARCH_EP93XX && MACH_TS72XX
+ help
+ FPGA manager driver support for the Altera Cyclone II FPGA
+ present on the TS-73xx SBC boards.
+
+config FPGA_MGR_XILINX_SPI
+ tristate "Xilinx Configuration over Slave Serial (SPI)"
+ depends on SPI
+ help
+ FPGA manager driver support for Xilinx FPGA configuration
+ over slave serial interface.
+
config FPGA_MGR_ZYNQ_FPGA
tristate "Xilinx Zynq FPGA"
depends on ARCH_ZYNQ || COMPILE_TEST
@@ -63,6 +83,28 @@ config ALTERA_FREEZE_BRIDGE
isolate one region of the FPGA from the busses while that
region is being reprogrammed.
+config ALTERA_PR_IP_CORE
+ tristate "Altera Partial Reconfiguration IP Core"
+ help
+ Core driver support for Altera Partial Reconfiguration IP component
+
+config ALTERA_PR_IP_CORE_PLAT
+ tristate "Platform support of Altera Partial Reconfiguration IP Core"
+ depends on ALTERA_PR_IP_CORE && OF && HAS_IOMEM
+ help
+ Platform driver support for Altera Partial Reconfiguration IP
+ component
+
+config XILINX_PR_DECOUPLER
+ tristate "Xilinx LogiCORE PR Decoupler"
+ depends on FPGA_BRIDGE
+ depends on HAS_IOMEM
+ help
+ Say Y to enable drivers for Xilinx LogiCORE PR Decoupler.
+ The PR Decoupler exists in the FPGA fabric to isolate one
+ region of the FPGA from the busses while that region is
+ being reprogrammed during partial reconfig.
+
endif # FPGA
endmenu
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 8df07bcf42a6..2a4f0218145c 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -6,14 +6,20 @@
obj-$(CONFIG_FPGA) += fpga-mgr.o
# FPGA Manager Drivers
+obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o
obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o
obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o
+obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o
+obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o
obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o
+obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o
+obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o
# FPGA Bridge Drivers
obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o
obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o
obj-$(CONFIG_ALTERA_FREEZE_BRIDGE) += altera-freeze-bridge.o
+obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o
# High Level Interfaces
obj-$(CONFIG_FPGA_REGION) += fpga-region.o
diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c
index 8dcd9fb22cb9..6159cfcf78a2 100644
--- a/drivers/fpga/altera-freeze-bridge.c
+++ b/drivers/fpga/altera-freeze-bridge.c
@@ -28,6 +28,7 @@
#define FREEZE_CSR_REG_VERSION 12
#define FREEZE_CSR_SUPPORTED_VERSION 2
+#define FREEZE_CSR_OFFICIAL_VERSION 0xad000003
#define FREEZE_CSR_STATUS_FREEZE_REQ_DONE BIT(0)
#define FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE BIT(1)
@@ -203,7 +204,7 @@ static int altera_freeze_br_enable_show(struct fpga_bridge *bridge)
return priv->enable;
}
-static struct fpga_bridge_ops altera_freeze_br_br_ops = {
+static const struct fpga_bridge_ops altera_freeze_br_br_ops = {
.enable_set = altera_freeze_br_enable_set,
.enable_show = altera_freeze_br_enable_show,
};
@@ -218,6 +219,7 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
+ void __iomem *base_addr;
struct altera_freeze_br_data *priv;
struct resource *res;
u32 status, revision;
@@ -225,26 +227,32 @@ static int altera_freeze_br_probe(struct platform_device *pdev)
if (!np)
return -ENODEV;
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base_addr))
+ return PTR_ERR(base_addr);
+
+ revision = readl(base_addr + FREEZE_CSR_REG_VERSION);
+ if ((revision != FREEZE_CSR_SUPPORTED_VERSION) &&
+ (revision != FREEZE_CSR_OFFICIAL_VERSION)) {
+ dev_err(dev,
+ "%s unexpected revision 0x%x != 0x%x != 0x%x\n",
+ __func__, revision, FREEZE_CSR_SUPPORTED_VERSION,
+ FREEZE_CSR_OFFICIAL_VERSION);
+ return -EINVAL;
+ }
+
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- priv->base_addr = devm_ioremap_resource(dev, res);
- if (IS_ERR(priv->base_addr))
- return PTR_ERR(priv->base_addr);
-
- status = readl(priv->base_addr + FREEZE_CSR_STATUS_OFFSET);
+ status = readl(base_addr + FREEZE_CSR_STATUS_OFFSET);
if (status & FREEZE_CSR_STATUS_UNFREEZE_REQ_DONE)
priv->enable = 1;
- revision = readl(priv->base_addr + FREEZE_CSR_REG_VERSION);
- if (revision != FREEZE_CSR_SUPPORTED_VERSION)
- dev_warn(dev,
- "%s Freeze Controller unexpected revision %d != %d\n",
- __func__, revision, FREEZE_CSR_SUPPORTED_VERSION);
+ priv->base_addr = base_addr;
return fpga_bridge_register(dev, FREEZE_BRIDGE_NAME,
&altera_freeze_br_br_ops, priv);
diff --git a/drivers/fpga/altera-hps2fpga.c b/drivers/fpga/altera-hps2fpga.c
index 4b354c79be31..3066b805f2d0 100644
--- a/drivers/fpga/altera-hps2fpga.c
+++ b/drivers/fpga/altera-hps2fpga.c
@@ -181,15 +181,18 @@ static int alt_fpga_bridge_probe(struct platform_device *pdev)
(enable ? "enabling" : "disabling"));
ret = _alt_hps2fpga_enable_set(priv, enable);
- if (ret) {
- fpga_bridge_unregister(&pdev->dev);
- return ret;
- }
+ if (ret)
+ goto err;
}
}
- return fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
- priv);
+ ret = fpga_bridge_register(dev, priv->name, &altera_hps2fpga_br_ops,
+ priv);
+err:
+ if (ret)
+ clk_disable_unprepare(priv->clk);
+
+ return ret;
}
static int alt_fpga_bridge_remove(struct platform_device *pdev)
diff --git a/drivers/fpga/altera-pr-ip-core-plat.c b/drivers/fpga/altera-pr-ip-core-plat.c
new file mode 100644
index 000000000000..8fb36b8b4648
--- /dev/null
+++ b/drivers/fpga/altera-pr-ip-core-plat.c
@@ -0,0 +1,68 @@
+/*
+ * Driver for Altera Partial Reconfiguration IP Core
+ *
+ * Copyright (C) 2016-2017 Intel Corporation
+ *
+ * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation
+ * by Alan Tull <atull@opensource.altera.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/fpga/altera-pr-ip-core.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+
+static int alt_pr_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ void __iomem *reg_base;
+ struct resource *res;
+
+ /* First mmio base is for register access */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ reg_base = devm_ioremap_resource(dev, res);
+
+ if (IS_ERR(reg_base))
+ return PTR_ERR(reg_base);
+
+ return alt_pr_register(dev, reg_base);
+}
+
+static int alt_pr_platform_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ return alt_pr_unregister(dev);
+}
+
+static const struct of_device_id alt_pr_of_match[] = {
+ { .compatible = "altr,a10-pr-ip", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, alt_pr_of_match);
+
+static struct platform_driver alt_pr_platform_driver = {
+ .probe = alt_pr_platform_probe,
+ .remove = alt_pr_platform_remove,
+ .driver = {
+ .name = "alt_a10_pr_ip",
+ .of_match_table = alt_pr_of_match,
+ },
+};
+
+module_platform_driver(alt_pr_platform_driver);
+MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>");
+MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Platform Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/altera-pr-ip-core.c b/drivers/fpga/altera-pr-ip-core.c
new file mode 100644
index 000000000000..a7b31f9797ce
--- /dev/null
+++ b/drivers/fpga/altera-pr-ip-core.c
@@ -0,0 +1,220 @@
+/*
+ * Driver for Altera Partial Reconfiguration IP Core
+ *
+ * Copyright (C) 2016-2017 Intel Corporation
+ *
+ * Based on socfpga-a10.c Copyright (C) 2015-2016 Altera Corporation
+ * by Alan Tull <atull@opensource.altera.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/delay.h>
+#include <linux/fpga/altera-pr-ip-core.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/module.h>
+
+#define ALT_PR_DATA_OFST 0x00
+#define ALT_PR_CSR_OFST 0x04
+
+#define ALT_PR_CSR_PR_START BIT(0)
+#define ALT_PR_CSR_STATUS_SFT 2
+#define ALT_PR_CSR_STATUS_MSK (7 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_NRESET (0 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_PR_ERR (1 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_CRC_ERR (2 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_BAD_BITS (3 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_PR_IN_PROG (4 << ALT_PR_CSR_STATUS_SFT)
+#define ALT_PR_CSR_STATUS_PR_SUCCESS (5 << ALT_PR_CSR_STATUS_SFT)
+
+struct alt_pr_priv {
+ void __iomem *reg_base;
+};
+
+static enum fpga_mgr_states alt_pr_fpga_state(struct fpga_manager *mgr)
+{
+ struct alt_pr_priv *priv = mgr->priv;
+ const char *err = "unknown";
+ enum fpga_mgr_states ret = FPGA_MGR_STATE_UNKNOWN;
+ u32 val;
+
+ val = readl(priv->reg_base + ALT_PR_CSR_OFST);
+
+ val &= ALT_PR_CSR_STATUS_MSK;
+
+ switch (val) {
+ case ALT_PR_CSR_STATUS_NRESET:
+ return FPGA_MGR_STATE_RESET;
+
+ case ALT_PR_CSR_STATUS_PR_ERR:
+ err = "pr error";
+ ret = FPGA_MGR_STATE_WRITE_ERR;
+ break;
+
+ case ALT_PR_CSR_STATUS_CRC_ERR:
+ err = "crc error";
+ ret = FPGA_MGR_STATE_WRITE_ERR;
+ break;
+
+ case ALT_PR_CSR_STATUS_BAD_BITS:
+ err = "bad bits";
+ ret = FPGA_MGR_STATE_WRITE_ERR;
+ break;
+
+ case ALT_PR_CSR_STATUS_PR_IN_PROG:
+ return FPGA_MGR_STATE_WRITE;
+
+ case ALT_PR_CSR_STATUS_PR_SUCCESS:
+ return FPGA_MGR_STATE_OPERATING;
+
+ default:
+ break;
+ }
+
+ dev_err(&mgr->dev, "encountered error code %d (%s) in %s()\n",
+ val, err, __func__);
+ return ret;
+}
+
+static int alt_pr_fpga_write_init(struct fpga_manager *mgr,
+ struct fpga_image_info *info,
+ const char *buf, size_t count)
+{
+ struct alt_pr_priv *priv = mgr->priv;
+ u32 val;
+
+ if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
+ dev_err(&mgr->dev, "%s Partial Reconfiguration flag not set\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ val = readl(priv->reg_base + ALT_PR_CSR_OFST);
+
+ if (val & ALT_PR_CSR_PR_START) {
+ dev_err(&mgr->dev,
+ "%s Partial Reconfiguration already started\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ writel(val | ALT_PR_CSR_PR_START, priv->reg_base + ALT_PR_CSR_OFST);
+
+ return 0;
+}
+
+static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf,
+ size_t count)
+{
+ struct alt_pr_priv *priv = mgr->priv;
+ u32 *buffer_32 = (u32 *)buf;
+ size_t i = 0;
+
+ if (count <= 0)
+ return -EINVAL;
+
+ /* Write out the complete 32-bit chunks */
+ while (count >= sizeof(u32)) {
+ writel(buffer_32[i++], priv->reg_base);
+ count -= sizeof(u32);
+ }
+
+ /* Write out remaining non 32-bit chunks */
+ switch (count) {
+ case 3:
+ writel(buffer_32[i++] & 0x00ffffff, priv->reg_base);
+ break;
+ case 2:
+ writel(buffer_32[i++] & 0x0000ffff, priv->reg_base);
+ break;
+ case 1:
+ writel(buffer_32[i++] & 0x000000ff, priv->reg_base);
+ break;
+ case 0:
+ break;
+ default:
+ /* This will never happen */
+ return -EFAULT;
+ }
+
+ if (alt_pr_fpga_state(mgr) == FPGA_MGR_STATE_WRITE_ERR)
+ return -EIO;
+
+ return 0;
+}
+
+static int alt_pr_fpga_write_complete(struct fpga_manager *mgr,
+ struct fpga_image_info *info)
+{
+ u32 i = 0;
+
+ do {
+ switch (alt_pr_fpga_state(mgr)) {
+ case FPGA_MGR_STATE_WRITE_ERR:
+ return -EIO;
+
+ case FPGA_MGR_STATE_OPERATING:
+ dev_info(&mgr->dev,
+ "successful partial reconfiguration\n");
+ return 0;
+
+ default:
+ break;
+ }
+ udelay(1);
+ } while (info->config_complete_timeout_us > i++);
+
+ dev_err(&mgr->dev, "timed out waiting for write to complete\n");
+ return -ETIMEDOUT;
+}
+
+static const struct fpga_manager_ops alt_pr_ops = {
+ .state = alt_pr_fpga_state,
+ .write_init = alt_pr_fpga_write_init,
+ .write = alt_pr_fpga_write,
+ .write_complete = alt_pr_fpga_write_complete,
+};
+
+int alt_pr_register(struct device *dev, void __iomem *reg_base)
+{
+ struct alt_pr_priv *priv;
+ u32 val;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->reg_base = reg_base;
+
+ val = readl(priv->reg_base + ALT_PR_CSR_OFST);
+
+ dev_dbg(dev, "%s status=%d start=%d\n", __func__,
+ (val & ALT_PR_CSR_STATUS_MSK) >> ALT_PR_CSR_STATUS_SFT,
+ (int)(val & ALT_PR_CSR_PR_START));
+
+ return fpga_mgr_register(dev, dev_name(dev), &alt_pr_ops, priv);
+}
+EXPORT_SYMBOL_GPL(alt_pr_register);
+
+int alt_pr_unregister(struct device *dev)
+{
+ dev_dbg(dev, "%s\n", __func__);
+
+ fpga_mgr_unregister(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(alt_pr_unregister);
+
+MODULE_AUTHOR("Matthew Gerlach <matthew.gerlach@linux.intel.com>");
+MODULE_DESCRIPTION("Altera Partial Reconfiguration IP Core");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c
index 33ee83e6373c..9651aa56244a 100644
--- a/drivers/fpga/fpga-bridge.c
+++ b/drivers/fpga/fpga-bridge.c
@@ -27,7 +27,7 @@ static DEFINE_IDA(fpga_bridge_ida);
static struct class *fpga_bridge_class;
/* Lock for adding/removing bridges to linked lists*/
-spinlock_t bridge_list_lock;
+static spinlock_t bridge_list_lock;
static int fpga_bridge_of_node_match(struct device *dev, const void *data)
{
@@ -146,11 +146,9 @@ EXPORT_SYMBOL_GPL(fpga_bridge_put);
int fpga_bridges_enable(struct list_head *bridge_list)
{
struct fpga_bridge *bridge;
- struct list_head *node;
int ret;
- list_for_each(node, bridge_list) {
- bridge = list_entry(node, struct fpga_bridge, node);
+ list_for_each_entry(bridge, bridge_list, node) {
ret = fpga_bridge_enable(bridge);
if (ret)
return ret;
@@ -172,11 +170,9 @@ EXPORT_SYMBOL_GPL(fpga_bridges_enable);
int fpga_bridges_disable(struct list_head *bridge_list)
{
struct fpga_bridge *bridge;
- struct list_head *node;
int ret;
- list_for_each(node, bridge_list) {
- bridge = list_entry(node, struct fpga_bridge, node);
+ list_for_each_entry(bridge, bridge_list, node) {
ret = fpga_bridge_disable(bridge);
if (ret)
return ret;
@@ -196,13 +192,10 @@ EXPORT_SYMBOL_GPL(fpga_bridges_disable);
*/
void fpga_bridges_put(struct list_head *bridge_list)
{
- struct fpga_bridge *bridge;
- struct list_head *node, *next;
+ struct fpga_bridge *bridge, *next;
unsigned long flags;
- list_for_each_safe(node, next, bridge_list) {
- bridge = list_entry(node, struct fpga_bridge, node);
-
+ list_for_each_entry_safe(bridge, next, bridge_list, node) {
fpga_bridge_put(bridge);
spin_lock_irqsave(&bridge_list_lock, flags);
diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c
index 86d2cb203533..188ffefa3cc3 100644
--- a/drivers/fpga/fpga-mgr.c
+++ b/drivers/fpga/fpga-mgr.c
@@ -361,7 +361,7 @@ static struct attribute *fpga_mgr_attrs[] = {
};
ATTRIBUTE_GROUPS(fpga_mgr);
-struct fpga_manager *__fpga_mgr_get(struct device *dev)
+static struct fpga_manager *__fpga_mgr_get(struct device *dev)
{
struct fpga_manager *mgr;
int ret = -ENODEV;
diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c
index 3222fdbad75a..3b6b2f4182a1 100644
--- a/drivers/fpga/fpga-region.c
+++ b/drivers/fpga/fpga-region.c
@@ -245,7 +245,8 @@ static int fpga_region_program_fpga(struct fpga_region *region,
mgr = fpga_region_get_manager(region);
if (IS_ERR(mgr)) {
pr_err("failed to get fpga region manager\n");
- return PTR_ERR(mgr);
+ ret = PTR_ERR(mgr);
+ goto err_put_region;
}
ret = fpga_region_get_bridges(region, overlay);
@@ -281,6 +282,7 @@ err_put_br:
fpga_bridges_put(&region->bridge_list);
err_put_mgr:
fpga_mgr_put(mgr);
+err_put_region:
fpga_region_put(region);
return ret;
@@ -337,8 +339,9 @@ static int child_regions_with_firmware(struct device_node *overlay)
* The overlay must add either firmware-name or external-fpga-config property
* to the FPGA Region.
*
- * firmware-name : program the FPGA
- * external-fpga-config : FPGA is already programmed
+ * firmware-name : program the FPGA
+ * external-fpga-config : FPGA is already programmed
+ * encrypted-fpga-config : FPGA bitstream is encrypted
*
* The overlay can add other FPGA regions, but child FPGA regions cannot have a
* firmware-name property since those regions don't exist yet.
@@ -373,6 +376,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region,
if (of_property_read_bool(nd->overlay, "external-fpga-config"))
info->flags |= FPGA_MGR_EXTERNAL_CONFIG;
+ if (of_property_read_bool(nd->overlay, "encrypted-fpga-config"))
+ info->flags |= FPGA_MGR_ENCRYPTED_BITSTREAM;
+
of_property_read_string(nd->overlay, "firmware-name", &firmware_name);
of_property_read_u32(nd->overlay, "region-unfreeze-timeout-us",
@@ -381,6 +387,9 @@ static int fpga_region_notify_pre_apply(struct fpga_region *region,
of_property_read_u32(nd->overlay, "region-freeze-timeout-us",
&info->disable_timeout_us);
+ of_property_read_u32(nd->overlay, "config-complete-timeout-us",
+ &info->config_complete_timeout_us);
+
/* If FPGA was externally programmed, don't specify firmware */
if ((info->flags & FPGA_MGR_EXTERNAL_CONFIG) && firmware_name) {
pr_err("error: specified firmware and external-fpga-config");
diff --git a/drivers/fpga/ice40-spi.c b/drivers/fpga/ice40-spi.c
new file mode 100644
index 000000000000..7fca82023062
--- /dev/null
+++ b/drivers/fpga/ice40-spi.c
@@ -0,0 +1,207 @@
+/*
+ * FPGA Manager Driver for Lattice iCE40.
+ *
+ * Copyright (c) 2016 Joel Holdsworth
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This driver adds support to the FPGA manager for configuring the SRAM of
+ * Lattice iCE40 FPGAs through slave SPI.
+ */
+
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/stringify.h>
+
+#define ICE40_SPI_MAX_SPEED 25000000 /* Hz */
+#define ICE40_SPI_MIN_SPEED 1000000 /* Hz */
+
+#define ICE40_SPI_RESET_DELAY 1 /* us (>200ns) */
+#define ICE40_SPI_HOUSEKEEPING_DELAY 1200 /* us */
+
+#define ICE40_SPI_NUM_ACTIVATION_BYTES DIV_ROUND_UP(49, 8)
+
+struct ice40_fpga_priv {
+ struct spi_device *dev;
+ struct gpio_desc *reset;
+ struct gpio_desc *cdone;
+};
+
+static enum fpga_mgr_states ice40_fpga_ops_state(struct fpga_manager *mgr)
+{
+ struct ice40_fpga_priv *priv = mgr->priv;
+
+ return gpiod_get_value(priv->cdone) ? FPGA_MGR_STATE_OPERATING :
+ FPGA_MGR_STATE_UNKNOWN;
+}
+
+static int ice40_fpga_ops_write_init(struct fpga_manager *mgr,
+ struct fpga_image_info *info,
+ const char *buf, size_t count)
+{
+ struct ice40_fpga_priv *priv = mgr->priv;
+ struct spi_device *dev = priv->dev;
+ struct spi_message message;
+ struct spi_transfer assert_cs_then_reset_delay = {
+ .cs_change = 1,
+ .delay_usecs = ICE40_SPI_RESET_DELAY
+ };
+ struct spi_transfer housekeeping_delay_then_release_cs = {
+ .delay_usecs = ICE40_SPI_HOUSEKEEPING_DELAY
+ };
+ int ret;
+
+ if ((info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
+ dev_err(&dev->dev,
+ "Partial reconfiguration is not supported\n");
+ return -ENOTSUPP;
+ }
+
+ /* Lock the bus, assert CRESET_B and SS_B and delay >200ns */
+ spi_bus_lock(dev->master);
+
+ gpiod_set_value(priv->reset, 1);
+
+ spi_message_init(&message);
+ spi_message_add_tail(&assert_cs_then_reset_delay, &message);
+ ret = spi_sync_locked(dev, &message);
+
+ /* Come out of reset */
+ gpiod_set_value(priv->reset, 0);
+
+ /* Abort if the chip-select failed */
+ if (ret)
+ goto fail;
+
+ /* Check CDONE is de-asserted i.e. the FPGA is reset */
+ if (gpiod_get_value(priv->cdone)) {
+ dev_err(&dev->dev, "Device reset failed, CDONE is asserted\n");
+ ret = -EIO;
+ goto fail;
+ }
+
+ /* Wait for the housekeeping to complete, and release SS_B */
+ spi_message_init(&message);
+ spi_message_add_tail(&housekeeping_delay_then_release_cs, &message);
+ ret = spi_sync_locked(dev, &message);
+
+fail:
+ spi_bus_unlock(dev->master);
+
+ return ret;
+}
+
+static int ice40_fpga_ops_write(struct fpga_manager *mgr,
+ const char *buf, size_t count)
+{
+ struct ice40_fpga_priv *priv = mgr->priv;
+
+ return spi_write(priv->dev, buf, count);
+}
+
+static int ice40_fpga_ops_write_complete(struct fpga_manager *mgr,
+ struct fpga_image_info *info)
+{
+ struct ice40_fpga_priv *priv = mgr->priv;
+ struct spi_device *dev = priv->dev;
+ const u8 padding[ICE40_SPI_NUM_ACTIVATION_BYTES] = {0};
+
+ /* Check CDONE is asserted */
+ if (!gpiod_get_value(priv->cdone)) {
+ dev_err(&dev->dev,
+ "CDONE was not asserted after firmware transfer\n");
+ return -EIO;
+ }
+
+ /* Send of zero-padding to activate the firmware */
+ return spi_write(dev, padding, sizeof(padding));
+}
+
+static const struct fpga_manager_ops ice40_fpga_ops = {
+ .state = ice40_fpga_ops_state,
+ .write_init = ice40_fpga_ops_write_init,
+ .write = ice40_fpga_ops_write,
+ .write_complete = ice40_fpga_ops_write_complete,
+};
+
+static int ice40_fpga_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct ice40_fpga_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = spi;
+
+ /* Check board setup data. */
+ if (spi->max_speed_hz > ICE40_SPI_MAX_SPEED) {
+ dev_err(dev, "SPI speed is too high, maximum speed is "
+ __stringify(ICE40_SPI_MAX_SPEED) "\n");
+ return -EINVAL;
+ }
+
+ if (spi->max_speed_hz < ICE40_SPI_MIN_SPEED) {
+ dev_err(dev, "SPI speed is too low, minimum speed is "
+ __stringify(ICE40_SPI_MIN_SPEED) "\n");
+ return -EINVAL;
+ }
+
+ if (spi->mode & SPI_CPHA) {
+ dev_err(dev, "Bad SPI mode, CPHA not supported\n");
+ return -EINVAL;
+ }
+
+ /* Set up the GPIOs */
+ priv->cdone = devm_gpiod_get(dev, "cdone", GPIOD_IN);
+ if (IS_ERR(priv->cdone)) {
+ ret = PTR_ERR(priv->cdone);
+ dev_err(dev, "Failed to get CDONE GPIO: %d\n", ret);
+ return ret;
+ }
+
+ priv->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->reset)) {
+ ret = PTR_ERR(priv->reset);
+ dev_err(dev, "Failed to get CRESET_B GPIO: %d\n", ret);
+ return ret;
+ }
+
+ /* Register with the FPGA manager */
+ return fpga_mgr_register(dev, "Lattice iCE40 FPGA Manager",
+ &ice40_fpga_ops, priv);
+}
+
+static int ice40_fpga_remove(struct spi_device *spi)
+{
+ fpga_mgr_unregister(&spi->dev);
+ return 0;
+}
+
+static const struct of_device_id ice40_fpga_of_match[] = {
+ { .compatible = "lattice,ice40-fpga-mgr", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ice40_fpga_of_match);
+
+static struct spi_driver ice40_fpga_driver = {
+ .probe = ice40_fpga_probe,
+ .remove = ice40_fpga_remove,
+ .driver = {
+ .name = "ice40spi",
+ .of_match_table = of_match_ptr(ice40_fpga_of_match),
+ },
+};
+
+module_spi_driver(ice40_fpga_driver);
+
+MODULE_AUTHOR("Joel Holdsworth <joel@airwebreathe.org.uk>");
+MODULE_DESCRIPTION("Lattice iCE40 FPGA Manager");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/ts73xx-fpga.c b/drivers/fpga/ts73xx-fpga.c
new file mode 100644
index 000000000000..f6a96b42e2ca
--- /dev/null
+++ b/drivers/fpga/ts73xx-fpga.c
@@ -0,0 +1,156 @@
+/*
+ * Technologic Systems TS-73xx SBC FPGA loader
+ *
+ * Copyright (C) 2016 Florian Fainelli <f.fainelli@gmail.com>
+ *
+ * FPGA Manager Driver for the on-board Altera Cyclone II FPGA found on
+ * TS-7300, heavily based on load_fpga.c in their vendor tree.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <linux/iopoll.h>
+#include <linux/fpga/fpga-mgr.h>
+
+#define TS73XX_FPGA_DATA_REG 0
+#define TS73XX_FPGA_CONFIG_REG 1
+
+#define TS73XX_FPGA_WRITE_DONE 0x1
+#define TS73XX_FPGA_WRITE_DONE_TIMEOUT 1000 /* us */
+#define TS73XX_FPGA_RESET 0x2
+#define TS73XX_FPGA_RESET_LOW_DELAY 30 /* us */
+#define TS73XX_FPGA_RESET_HIGH_DELAY 80 /* us */
+#define TS73XX_FPGA_LOAD_OK 0x4
+#define TS73XX_FPGA_CONFIG_LOAD 0x8
+
+struct ts73xx_fpga_priv {
+ void __iomem *io_base;
+ struct device *dev;
+};
+
+static enum fpga_mgr_states ts73xx_fpga_state(struct fpga_manager *mgr)
+{
+ return FPGA_MGR_STATE_UNKNOWN;
+}
+
+static int ts73xx_fpga_write_init(struct fpga_manager *mgr,
+ struct fpga_image_info *info,
+ const char *buf, size_t count)
+{
+ struct ts73xx_fpga_priv *priv = mgr->priv;
+
+ /* Reset the FPGA */
+ writeb(0, priv->io_base + TS73XX_FPGA_CONFIG_REG);
+ udelay(TS73XX_FPGA_RESET_LOW_DELAY);
+ writeb(TS73XX_FPGA_RESET, priv->io_base + TS73XX_FPGA_CONFIG_REG);
+ udelay(TS73XX_FPGA_RESET_HIGH_DELAY);
+
+ return 0;
+}
+
+static int ts73xx_fpga_write(struct fpga_manager *mgr, const char *buf,
+ size_t count)
+{
+ struct ts73xx_fpga_priv *priv = mgr->priv;
+ size_t i = 0;
+ int ret;
+ u8 reg;
+
+ while (count--) {
+ ret = readb_poll_timeout(priv->io_base + TS73XX_FPGA_CONFIG_REG,
+ reg, !(reg & TS73XX_FPGA_WRITE_DONE),
+ 1, TS73XX_FPGA_WRITE_DONE_TIMEOUT);
+ if (ret < 0)
+ return ret;
+
+ writeb(buf[i], priv->io_base + TS73XX_FPGA_DATA_REG);
+ i++;
+ }
+
+ return 0;
+}
+
+static int ts73xx_fpga_write_complete(struct fpga_manager *mgr,
+ struct fpga_image_info *info)
+{
+ struct ts73xx_fpga_priv *priv = mgr->priv;
+ u8 reg;
+
+ usleep_range(1000, 2000);
+ reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
+ reg |= TS73XX_FPGA_CONFIG_LOAD;
+ writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
+
+ usleep_range(1000, 2000);
+ reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
+ reg &= ~TS73XX_FPGA_CONFIG_LOAD;
+ writeb(reg, priv->io_base + TS73XX_FPGA_CONFIG_REG);
+
+ reg = readb(priv->io_base + TS73XX_FPGA_CONFIG_REG);
+ if ((reg & TS73XX_FPGA_LOAD_OK) != TS73XX_FPGA_LOAD_OK)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static const struct fpga_manager_ops ts73xx_fpga_ops = {
+ .state = ts73xx_fpga_state,
+ .write_init = ts73xx_fpga_write_init,
+ .write = ts73xx_fpga_write,
+ .write_complete = ts73xx_fpga_write_complete,
+};
+
+static int ts73xx_fpga_probe(struct platform_device *pdev)
+{
+ struct device *kdev = &pdev->dev;
+ struct ts73xx_fpga_priv *priv;
+ struct resource *res;
+
+ priv = devm_kzalloc(kdev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = kdev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->io_base = devm_ioremap_resource(kdev, res);
+ if (IS_ERR(priv->io_base)) {
+ dev_err(kdev, "unable to remap registers\n");
+ return PTR_ERR(priv->io_base);
+ }
+
+ return fpga_mgr_register(kdev, "TS-73xx FPGA Manager",
+ &ts73xx_fpga_ops, priv);
+}
+
+static int ts73xx_fpga_remove(struct platform_device *pdev)
+{
+ fpga_mgr_unregister(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver ts73xx_fpga_driver = {
+ .driver = {
+ .name = "ts73xx-fpga-mgr",
+ },
+ .probe = ts73xx_fpga_probe,
+ .remove = ts73xx_fpga_remove,
+};
+module_platform_driver(ts73xx_fpga_driver);
+
+MODULE_AUTHOR("Florian Fainelli <f.fainelli@gmail.com>");
+MODULE_DESCRIPTION("TS-73xx FPGA Manager driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/xilinx-pr-decoupler.c b/drivers/fpga/xilinx-pr-decoupler.c
new file mode 100644
index 000000000000..e359930bebc8
--- /dev/null
+++ b/drivers/fpga/xilinx-pr-decoupler.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2017, National Instruments Corp.
+ * Copyright (c) 2017, Xilix Inc
+ *
+ * FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration
+ * Decoupler IP Core.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/fpga/fpga-bridge.h>
+
+#define CTRL_CMD_DECOUPLE BIT(0)
+#define CTRL_CMD_COUPLE 0
+#define CTRL_OFFSET 0
+
+struct xlnx_pr_decoupler_data {
+ void __iomem *io_base;
+ struct clk *clk;
+};
+
+static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d,
+ u32 offset, u32 val)
+{
+ writel(val, d->io_base + offset);
+}
+
+static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d,
+ u32 offset)
+{
+ return readl(d->io_base + offset);
+}
+
+static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable)
+{
+ int err;
+ struct xlnx_pr_decoupler_data *priv = bridge->priv;
+
+ err = clk_enable(priv->clk);
+ if (err)
+ return err;
+
+ if (enable)
+ xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE);
+ else
+ xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE);
+
+ clk_disable(priv->clk);
+
+ return 0;
+}
+
+static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge)
+{
+ const struct xlnx_pr_decoupler_data *priv = bridge->priv;
+ u32 status;
+ int err;
+
+ err = clk_enable(priv->clk);
+ if (err)
+ return err;
+
+ status = readl(priv->io_base);
+
+ clk_disable(priv->clk);
+
+ return !status;
+}
+
+static struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = {
+ .enable_set = xlnx_pr_decoupler_enable_set,
+ .enable_show = xlnx_pr_decoupler_enable_show,
+};
+
+static const struct of_device_id xlnx_pr_decoupler_of_match[] = {
+ { .compatible = "xlnx,pr-decoupler-1.00", },
+ { .compatible = "xlnx,pr-decoupler", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match);
+
+static int xlnx_pr_decoupler_probe(struct platform_device *pdev)
+{
+ struct xlnx_pr_decoupler_data *priv;
+ int err;
+ struct resource *res;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->io_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(priv->io_base))
+ return PTR_ERR(priv->io_base);
+
+ priv->clk = devm_clk_get(&pdev->dev, "aclk");
+ if (IS_ERR(priv->clk)) {
+ dev_err(&pdev->dev, "input clock not found\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ dev_err(&pdev->dev, "unable to enable clock\n");
+ return err;
+ }
+
+ clk_disable(priv->clk);
+
+ err = fpga_bridge_register(&pdev->dev, "Xilinx PR Decoupler",
+ &xlnx_pr_decoupler_br_ops, priv);
+
+ if (err) {
+ dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler");
+ clk_unprepare(priv->clk);
+ return err;
+ }
+
+ return 0;
+}
+
+static int xlnx_pr_decoupler_remove(struct platform_device *pdev)
+{
+ struct fpga_bridge *bridge = platform_get_drvdata(pdev);
+ struct xlnx_pr_decoupler_data *p = bridge->priv;
+
+ fpga_bridge_unregister(&pdev->dev);
+
+ clk_unprepare(p->clk);
+
+ return 0;
+}
+
+static struct platform_driver xlnx_pr_decoupler_driver = {
+ .probe = xlnx_pr_decoupler_probe,
+ .remove = xlnx_pr_decoupler_remove,
+ .driver = {
+ .name = "xlnx_pr_decoupler",
+ .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match),
+ },
+};
+
+module_platform_driver(xlnx_pr_decoupler_driver);
+
+MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler");
+MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>");
+MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/fpga/xilinx-spi.c b/drivers/fpga/xilinx-spi.c
new file mode 100644
index 000000000000..9b62a4c2a3df
--- /dev/null
+++ b/drivers/fpga/xilinx-spi.c
@@ -0,0 +1,198 @@
+/*
+ * Xilinx Spartan6 Slave Serial SPI Driver
+ *
+ * Copyright (C) 2017 DENX Software Engineering
+ *
+ * Anatolij Gustschin <agust@denx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * Manage Xilinx FPGA firmware that is loaded over SPI using
+ * the slave serial configuration interface.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+#include <linux/sizes.h>
+
+struct xilinx_spi_conf {
+ struct spi_device *spi;
+ struct gpio_desc *prog_b;
+ struct gpio_desc *done;
+};
+
+static enum fpga_mgr_states xilinx_spi_state(struct fpga_manager *mgr)
+{
+ struct xilinx_spi_conf *conf = mgr->priv;
+
+ if (!gpiod_get_value(conf->done))
+ return FPGA_MGR_STATE_RESET;
+
+ return FPGA_MGR_STATE_UNKNOWN;
+}
+
+static int xilinx_spi_write_init(struct fpga_manager *mgr,
+ struct fpga_image_info *info,
+ const char *buf, size_t count)
+{
+ struct xilinx_spi_conf *conf = mgr->priv;
+ const size_t prog_latency_7500us = 7500;
+ const size_t prog_pulse_1us = 1;
+
+ if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) {
+ dev_err(&mgr->dev, "Partial reconfiguration not supported.\n");
+ return -EINVAL;
+ }
+
+ gpiod_set_value(conf->prog_b, 1);
+
+ udelay(prog_pulse_1us); /* min is 500 ns */
+
+ gpiod_set_value(conf->prog_b, 0);
+
+ if (gpiod_get_value(conf->done)) {
+ dev_err(&mgr->dev, "Unexpected DONE pin state...\n");
+ return -EIO;
+ }
+
+ /* program latency */
+ usleep_range(prog_latency_7500us, prog_latency_7500us + 100);
+ return 0;
+}
+
+static int xilinx_spi_write(struct fpga_manager *mgr, const char *buf,
+ size_t count)
+{
+ struct xilinx_spi_conf *conf = mgr->priv;
+ const char *fw_data = buf;
+ const char *fw_data_end = fw_data + count;
+
+ while (fw_data < fw_data_end) {
+ size_t remaining, stride;
+ int ret;
+
+ remaining = fw_data_end - fw_data;
+ stride = min_t(size_t, remaining, SZ_4K);
+
+ ret = spi_write(conf->spi, fw_data, stride);
+ if (ret) {
+ dev_err(&mgr->dev, "SPI error in firmware write: %d\n",
+ ret);
+ return ret;
+ }
+ fw_data += stride;
+ }
+
+ return 0;
+}
+
+static int xilinx_spi_apply_cclk_cycles(struct xilinx_spi_conf *conf)
+{
+ struct spi_device *spi = conf->spi;
+ const u8 din_data[1] = { 0xff };
+ int ret;
+
+ ret = spi_write(conf->spi, din_data, sizeof(din_data));
+ if (ret)
+ dev_err(&spi->dev, "applying CCLK cycles failed: %d\n", ret);
+
+ return ret;
+}
+
+static int xilinx_spi_write_complete(struct fpga_manager *mgr,
+ struct fpga_image_info *info)
+{
+ struct xilinx_spi_conf *conf = mgr->priv;
+ unsigned long timeout;
+ int ret;
+
+ if (gpiod_get_value(conf->done))
+ return xilinx_spi_apply_cclk_cycles(conf);
+
+ timeout = jiffies + usecs_to_jiffies(info->config_complete_timeout_us);
+
+ while (time_before(jiffies, timeout)) {
+
+ ret = xilinx_spi_apply_cclk_cycles(conf);
+ if (ret)
+ return ret;
+
+ if (gpiod_get_value(conf->done))
+ return xilinx_spi_apply_cclk_cycles(conf);
+ }
+
+ dev_err(&mgr->dev, "Timeout after config data transfer.\n");
+ return -ETIMEDOUT;
+}
+
+static const struct fpga_manager_ops xilinx_spi_ops = {
+ .state = xilinx_spi_state,
+ .write_init = xilinx_spi_write_init,
+ .write = xilinx_spi_write,
+ .write_complete = xilinx_spi_write_complete,
+};
+
+static int xilinx_spi_probe(struct spi_device *spi)
+{
+ struct xilinx_spi_conf *conf;
+
+ conf = devm_kzalloc(&spi->dev, sizeof(*conf), GFP_KERNEL);
+ if (!conf)
+ return -ENOMEM;
+
+ conf->spi = spi;
+
+ /* PROGRAM_B is active low */
+ conf->prog_b = devm_gpiod_get(&spi->dev, "prog_b", GPIOD_OUT_LOW);
+ if (IS_ERR(conf->prog_b)) {
+ dev_err(&spi->dev, "Failed to get PROGRAM_B gpio: %ld\n",
+ PTR_ERR(conf->prog_b));
+ return PTR_ERR(conf->prog_b);
+ }
+
+ conf->done = devm_gpiod_get(&spi->dev, "done", GPIOD_IN);
+ if (IS_ERR(conf->done)) {
+ dev_err(&spi->dev, "Failed to get DONE gpio: %ld\n",
+ PTR_ERR(conf->done));
+ return PTR_ERR(conf->done);
+ }
+
+ return fpga_mgr_register(&spi->dev, "Xilinx Slave Serial FPGA Manager",
+ &xilinx_spi_ops, conf);
+}
+
+static int xilinx_spi_remove(struct spi_device *spi)
+{
+ fpga_mgr_unregister(&spi->dev);
+
+ return 0;
+}
+
+static const struct of_device_id xlnx_spi_of_match[] = {
+ { .compatible = "xlnx,fpga-slave-serial", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, xlnx_spi_of_match);
+
+static struct spi_driver xilinx_slave_spi_driver = {
+ .driver = {
+ .name = "xlnx-slave-spi",
+ .of_match_table = of_match_ptr(xlnx_spi_of_match),
+ },
+ .probe = xilinx_spi_probe,
+ .remove = xilinx_spi_remove,
+};
+
+module_spi_driver(xilinx_slave_spi_driver)
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
+MODULE_DESCRIPTION("Load Xilinx FPGA firmware over SPI");
diff --git a/drivers/fpga/zynq-fpga.c b/drivers/fpga/zynq-fpga.c
index 34cb98139442..70b15b303471 100644
--- a/drivers/fpga/zynq-fpga.c
+++ b/drivers/fpga/zynq-fpga.c
@@ -72,6 +72,10 @@
#define CTRL_PCAP_PR_MASK BIT(27)
/* Enable PCAP */
#define CTRL_PCAP_MODE_MASK BIT(26)
+/* Lower rate to allow decrypt on the fly */
+#define CTRL_PCAP_RATE_EN_MASK BIT(25)
+/* System booted in secure mode */
+#define CTRL_SEC_EN_MASK BIT(7)
/* Miscellaneous Control Register bit definitions */
/* Internal PCAP loopback */
@@ -266,6 +270,17 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
if (err)
return err;
+ /* check if bitstream is encrypted & and system's still secure */
+ if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM) {
+ ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
+ if (!(ctrl & CTRL_SEC_EN_MASK)) {
+ dev_err(&mgr->dev,
+ "System not secure, can't use crypted bitstreams\n");
+ err = -EINVAL;
+ goto out_err;
+ }
+ }
+
/* don't globally reset PL if we're doing partial reconfig */
if (!(info->flags & FPGA_MGR_PARTIAL_RECONFIG)) {
if (!zynq_fpga_has_sync(buf, count)) {
@@ -337,12 +352,19 @@ static int zynq_fpga_ops_write_init(struct fpga_manager *mgr,
/* set configuration register with following options:
* - enable PCAP interface
- * - set throughput for maximum speed
+ * - set throughput for maximum speed (if bistream not crypted)
* - set CPU in user mode
*/
ctrl = zynq_fpga_read(priv, CTRL_OFFSET);
- zynq_fpga_write(priv, CTRL_OFFSET,
- (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK | ctrl));
+ if (info->flags & FPGA_MGR_ENCRYPTED_BITSTREAM)
+ zynq_fpga_write(priv, CTRL_OFFSET,
+ (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
+ | CTRL_PCAP_RATE_EN_MASK | ctrl));
+ else
+ zynq_fpga_write(priv, CTRL_OFFSET,
+ (CTRL_PCAP_PR_MASK | CTRL_PCAP_MODE_MASK
+ | ctrl));
+
/* We expect that the command queue is empty right now. */
status = zynq_fpga_read(priv, STATUS_OFFSET);
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 1d1fa7248d63..5db44139cef8 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1035,18 +1035,14 @@ static int gpiochip_setup_dev(struct gpio_device *gdev)
cdev_init(&gdev->chrdev, &gpio_fileops);
gdev->chrdev.owner = THIS_MODULE;
- gdev->chrdev.kobj.parent = &gdev->dev.kobj;
gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id);
- status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1);
- if (status < 0)
- chip_warn(gdev->chip, "failed to add char device %d:%d\n",
- MAJOR(gpio_devt), gdev->id);
- else
- chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
- MAJOR(gpio_devt), gdev->id);
- status = device_add(&gdev->dev);
+
+ status = cdev_device_add(&gdev->chrdev, &gdev->dev);
if (status)
- goto err_remove_chardev;
+ return status;
+
+ chip_dbg(gdev->chip, "added GPIO chardev (%d:%d)\n",
+ MAJOR(gpio_devt), gdev->id);
status = gpiochip_sysfs_register(gdev);
if (status)
@@ -1061,9 +1057,7 @@ static int gpiochip_setup_dev(struct gpio_device *gdev)
return 0;
err_remove_device:
- device_del(&gdev->dev);
-err_remove_chardev:
- cdev_del(&gdev->chrdev);
+ cdev_device_del(&gdev->chrdev, &gdev->dev);
return status;
}
@@ -1347,8 +1341,7 @@ void gpiochip_remove(struct gpio_chip *chip)
* be removed, else it will be dangling until the last user is
* gone.
*/
- cdev_del(&gdev->chrdev);
- device_del(&gdev->dev);
+ cdev_device_del(&gdev->chrdev, &gdev->dev);
put_device(&gdev->dev);
}
EXPORT_SYMBOL_GPL(gpiochip_remove);
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 321b8833fa6f..736ac76d2a6a 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -333,7 +333,7 @@ static int create_gpadl_header(void *kbuffer, u32 size,
* Gpadl is u32 and we are using a pointer which could
* be 64-bit
* This is governed by the guest/host protocol and
- * so the hypervisor gurantees that this is ok.
+ * so the hypervisor guarantees that this is ok.
*/
for (i = 0; i < pfncurr; i++)
gpadl_body->pfn[i] = slow_virt_to_phys(
@@ -380,7 +380,7 @@ nomem:
}
/*
- * vmbus_establish_gpadl - Estabish a GPADL for the specified buffer
+ * vmbus_establish_gpadl - Establish a GPADL for the specified buffer
*
* @channel: a channel
* @kbuffer: from kmalloc or vmalloc
@@ -731,7 +731,7 @@ int vmbus_sendpacket_pagebuffer_ctl(struct vmbus_channel *channel,
/* Setup the descriptor */
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
desc.flags = flags;
- desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
+ desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
desc.length8 = (u16)(packetlen_aligned >> 3);
desc.transactionid = requestid;
desc.rangecount = pagecount;
@@ -792,7 +792,7 @@ int vmbus_sendpacket_mpb_desc(struct vmbus_channel *channel,
/* Setup the descriptor */
desc->type = VM_PKT_DATA_USING_GPA_DIRECT;
desc->flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
- desc->dataoffset8 = desc_size >> 3; /* in 8-bytes grandularity */
+ desc->dataoffset8 = desc_size >> 3; /* in 8-bytes granularity */
desc->length8 = (u16)(packetlen_aligned >> 3);
desc->transactionid = requestid;
desc->rangecount = 1;
@@ -842,7 +842,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel,
/* Setup the descriptor */
desc.type = VM_PKT_DATA_USING_GPA_DIRECT;
desc.flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED;
- desc.dataoffset8 = descsize >> 3; /* in 8-bytes grandularity */
+ desc.dataoffset8 = descsize >> 3; /* in 8-bytes granularity */
desc.length8 = (u16)(packetlen_aligned >> 3);
desc.transactionid = requestid;
desc.rangecount = 1;
diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c
index fbcb06352308..735f9363f2e4 100644
--- a/drivers/hv/channel_mgmt.c
+++ b/drivers/hv/channel_mgmt.c
@@ -1080,30 +1080,30 @@ static void vmbus_onversion_response(
}
/* Channel message dispatch table */
-struct vmbus_channel_message_table_entry
- channel_message_table[CHANNELMSG_COUNT] = {
- {CHANNELMSG_INVALID, 0, NULL},
- {CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer},
- {CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind},
- {CHANNELMSG_REQUESTOFFERS, 0, NULL},
- {CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered},
- {CHANNELMSG_OPENCHANNEL, 0, NULL},
- {CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result},
- {CHANNELMSG_CLOSECHANNEL, 0, NULL},
- {CHANNELMSG_GPADL_HEADER, 0, NULL},
- {CHANNELMSG_GPADL_BODY, 0, NULL},
- {CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created},
- {CHANNELMSG_GPADL_TEARDOWN, 0, NULL},
- {CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown},
- {CHANNELMSG_RELID_RELEASED, 0, NULL},
- {CHANNELMSG_INITIATE_CONTACT, 0, NULL},
- {CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response},
- {CHANNELMSG_UNLOAD, 0, NULL},
- {CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response},
- {CHANNELMSG_18, 0, NULL},
- {CHANNELMSG_19, 0, NULL},
- {CHANNELMSG_20, 0, NULL},
- {CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL},
+const struct vmbus_channel_message_table_entry
+channel_message_table[CHANNELMSG_COUNT] = {
+ { CHANNELMSG_INVALID, 0, NULL },
+ { CHANNELMSG_OFFERCHANNEL, 0, vmbus_onoffer },
+ { CHANNELMSG_RESCIND_CHANNELOFFER, 0, vmbus_onoffer_rescind },
+ { CHANNELMSG_REQUESTOFFERS, 0, NULL },
+ { CHANNELMSG_ALLOFFERS_DELIVERED, 1, vmbus_onoffers_delivered },
+ { CHANNELMSG_OPENCHANNEL, 0, NULL },
+ { CHANNELMSG_OPENCHANNEL_RESULT, 1, vmbus_onopen_result },
+ { CHANNELMSG_CLOSECHANNEL, 0, NULL },
+ { CHANNELMSG_GPADL_HEADER, 0, NULL },
+ { CHANNELMSG_GPADL_BODY, 0, NULL },
+ { CHANNELMSG_GPADL_CREATED, 1, vmbus_ongpadl_created },
+ { CHANNELMSG_GPADL_TEARDOWN, 0, NULL },
+ { CHANNELMSG_GPADL_TORNDOWN, 1, vmbus_ongpadl_torndown },
+ { CHANNELMSG_RELID_RELEASED, 0, NULL },
+ { CHANNELMSG_INITIATE_CONTACT, 0, NULL },
+ { CHANNELMSG_VERSION_RESPONSE, 1, vmbus_onversion_response },
+ { CHANNELMSG_UNLOAD, 0, NULL },
+ { CHANNELMSG_UNLOAD_RESPONSE, 1, vmbus_unload_response },
+ { CHANNELMSG_18, 0, NULL },
+ { CHANNELMSG_19, 0, NULL },
+ { CHANNELMSG_20, 0, NULL },
+ { CHANNELMSG_TL_CONNECT_REQUEST, 0, NULL },
};
/*
diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c
index a8366fec1458..fce27fb141cc 100644
--- a/drivers/hv/connection.c
+++ b/drivers/hv/connection.c
@@ -296,44 +296,47 @@ struct vmbus_channel *relid2channel(u32 relid)
/*
* vmbus_on_event - Process a channel event notification
+ *
+ * For batched channels (default) optimize host to guest signaling
+ * by ensuring:
+ * 1. While reading the channel, we disable interrupts from host.
+ * 2. Ensure that we process all posted messages from the host
+ * before returning from this callback.
+ * 3. Once we return, enable signaling from the host. Once this
+ * state is set we check to see if additional packets are
+ * available to read. In this case we repeat the process.
+ * If this tasklet has been running for a long time
+ * then reschedule ourselves.
*/
void vmbus_on_event(unsigned long data)
{
struct vmbus_channel *channel = (void *) data;
- void (*callback_fn)(void *);
+ unsigned long time_limit = jiffies + 2;
- /*
- * A channel once created is persistent even when there
- * is no driver handling the device. An unloading driver
- * sets the onchannel_callback to NULL on the same CPU
- * as where this interrupt is handled (in an interrupt context).
- * Thus, checking and invoking the driver specific callback takes
- * care of orderly unloading of the driver.
- */
- callback_fn = READ_ONCE(channel->onchannel_callback);
- if (unlikely(callback_fn == NULL))
- return;
-
- (*callback_fn)(channel->channel_callback_context);
-
- if (channel->callback_mode == HV_CALL_BATCHED) {
- /*
- * This callback reads the messages sent by the host.
- * We can optimize host to guest signaling by ensuring:
- * 1. While reading the channel, we disable interrupts from
- * host.
- * 2. Ensure that we process all posted messages from the host
- * before returning from this callback.
- * 3. Once we return, enable signaling from the host. Once this
- * state is set we check to see if additional packets are
- * available to read. In this case we repeat the process.
+ do {
+ void (*callback_fn)(void *);
+
+ /* A channel once created is persistent even when
+ * there is no driver handling the device. An
+ * unloading driver sets the onchannel_callback to NULL.
*/
- if (hv_end_read(&channel->inbound) != 0) {
- hv_begin_read(&channel->inbound);
+ callback_fn = READ_ONCE(channel->onchannel_callback);
+ if (unlikely(callback_fn == NULL))
+ return;
- tasklet_schedule(&channel->callback_event);
- }
- }
+ (*callback_fn)(channel->channel_callback_context);
+
+ if (channel->callback_mode != HV_CALL_BATCHED)
+ return;
+
+ if (likely(hv_end_read(&channel->inbound) == 0))
+ return;
+
+ hv_begin_read(&channel->inbound);
+ } while (likely(time_before(jiffies, time_limit)));
+
+ /* The time limit (2 jiffies) has been reached */
+ tasklet_schedule(&channel->callback_event);
}
/*
diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c
index 665a64f1611e..12e7baecb84e 100644
--- a/drivers/hv/hv.c
+++ b/drivers/hv/hv.c
@@ -254,7 +254,10 @@ int hv_synic_init(unsigned int cpu)
shared_sint.as_uint64 = 0;
shared_sint.vector = HYPERVISOR_CALLBACK_VECTOR;
shared_sint.masked = false;
- shared_sint.auto_eoi = true;
+ if (ms_hyperv.hints & HV_X64_DEPRECATING_AEOI_RECOMMENDED)
+ shared_sint.auto_eoi = false;
+ else
+ shared_sint.auto_eoi = true;
hv_set_synint_state(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT,
shared_sint.as_uint64);
diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c
index 5fd03e59cee5..f5728deff893 100644
--- a/drivers/hv/hv_balloon.c
+++ b/drivers/hv/hv_balloon.c
@@ -722,8 +722,6 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size,
5*HZ);
post_status(&dm_device);
}
-
- return;
}
static void hv_online_page(struct page *pg)
diff --git a/drivers/hv/hv_fcopy.c b/drivers/hv/hv_fcopy.c
index a5596a642ed0..daa75bd41f86 100644
--- a/drivers/hv/hv_fcopy.c
+++ b/drivers/hv/hv_fcopy.c
@@ -186,8 +186,6 @@ static void fcopy_send_data(struct work_struct *dummy)
}
}
kfree(smsg_out);
-
- return;
}
/*
diff --git a/drivers/hv/hv_kvp.c b/drivers/hv/hv_kvp.c
index a1adfe2cfb34..e99ff2ddad40 100644
--- a/drivers/hv/hv_kvp.c
+++ b/drivers/hv/hv_kvp.c
@@ -69,7 +69,7 @@ static const int fw_versions[] = {
*
* While the request/response protocol is guaranteed by the host, we further
* ensure this by serializing packet processing in this driver - we do not
- * read additional packets from the VMBUs until the current packet is fully
+ * read additional packets from the VMBUS until the current packet is fully
* handled.
*/
@@ -397,7 +397,7 @@ kvp_send_key(struct work_struct *dummy)
* the max lengths specified. We will however, reserve room
* for the string terminating character - in the utf16s_utf8s()
* function we limit the size of the buffer where the converted
- * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to gaurantee
+ * string is placed to HV_KVP_EXCHANGE_MAX_*_SIZE -1 to guarantee
* that the strings can be properly terminated!
*/
@@ -483,8 +483,6 @@ kvp_send_key(struct work_struct *dummy)
}
kfree(message);
-
- return;
}
/*
@@ -533,7 +531,7 @@ kvp_respond_to_host(struct hv_kvp_msg *msg_to_host, int error)
*/
if (error) {
/*
- * Something failed or we have timedout;
+ * Something failed or we have timed out;
* terminate the current host-side iteration.
*/
goto response_done;
@@ -607,8 +605,8 @@ response_done:
* This callback is invoked when we get a KVP message from the host.
* The host ensures that only one KVP transaction can be active at a time.
* KVP implementation in Linux needs to forward the key to a user-mde
- * component to retrive the corresponding value. Consequently, we cannot
- * respond to the host in the conext of this callback. Since the host
+ * component to retrieve the corresponding value. Consequently, we cannot
+ * respond to the host in the context of this callback. Since the host
* guarantees that at most only one transaction can be active at a time,
* we stash away the transaction state in a set of global variables.
*/
diff --git a/drivers/hv/hv_snapshot.c b/drivers/hv/hv_snapshot.c
index e659d1b94a57..6831efd73394 100644
--- a/drivers/hv/hv_snapshot.c
+++ b/drivers/hv/hv_snapshot.c
@@ -212,8 +212,6 @@ static void vss_send_op(void)
}
kfree(vss_msg);
-
- return;
}
static void vss_handle_request(struct work_struct *dummy)
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 884f83bba1ab..6113e915c50e 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -218,8 +218,8 @@ struct hv_per_cpu_context {
struct hv_context {
/* We only support running on top of Hyper-V
- * So at this point this really can only contain the Hyper-V ID
- */
+ * So at this point this really can only contain the Hyper-V ID
+ */
u64 guestid;
void *tsc_page;
@@ -248,14 +248,6 @@ struct hv_context {
extern struct hv_context hv_context;
-struct hv_ring_buffer_debug_info {
- u32 current_interrupt_mask;
- u32 current_read_index;
- u32 current_write_index;
- u32 bytes_avail_toread;
- u32 bytes_avail_towrite;
-};
-
/* Hv Interface */
extern int hv_init(void);
@@ -289,9 +281,6 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
void *buffer, u32 buflen, u32 *buffer_actual_len,
u64 *requestid, bool raw);
-void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
- struct hv_ring_buffer_debug_info *debug_info);
-
/*
* Maximum channels is determined by the size of the interrupt page
* which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt
@@ -376,7 +365,7 @@ struct vmbus_channel_message_table_entry {
void (*message_handler)(struct vmbus_channel_message_header *msg);
};
-extern struct vmbus_channel_message_table_entry
+extern const struct vmbus_channel_message_table_entry
channel_message_table[CHANNELMSG_COUNT];
@@ -403,17 +392,17 @@ int vmbus_post_msg(void *buffer, size_t buflen, bool can_sleep);
void vmbus_on_event(unsigned long data);
void vmbus_on_msg_dpc(unsigned long data);
-int hv_kvp_init(struct hv_util_service *);
+int hv_kvp_init(struct hv_util_service *srv);
void hv_kvp_deinit(void);
-void hv_kvp_onchannelcallback(void *);
+void hv_kvp_onchannelcallback(void *context);
-int hv_vss_init(struct hv_util_service *);
+int hv_vss_init(struct hv_util_service *srv);
void hv_vss_deinit(void);
-void hv_vss_onchannelcallback(void *);
+void hv_vss_onchannelcallback(void *context);
-int hv_fcopy_init(struct hv_util_service *);
+int hv_fcopy_init(struct hv_util_service *srv);
void hv_fcopy_deinit(void);
-void hv_fcopy_onchannelcallback(void *);
+void hv_fcopy_onchannelcallback(void *context);
void vmbus_initiate_unload(bool crash);
static inline void hv_poll_channel(struct vmbus_channel *channel,
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c
index c3f1a9e33cef..1f450c39a9b0 100644
--- a/drivers/hv/ring_buffer.c
+++ b/drivers/hv/ring_buffer.c
@@ -75,8 +75,6 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel)
*/
if (old_write == READ_ONCE(rbi->ring_buffer->read_index))
vmbus_setevent(channel);
-
- return;
}
/* Get the next write location for the specified ring buffer. */
@@ -210,6 +208,7 @@ void hv_ringbuffer_get_debuginfo(const struct hv_ring_buffer_info *ring_info,
ring_info->ring_buffer->interrupt_mask;
}
}
+EXPORT_SYMBOL_GPL(hv_ringbuffer_get_debuginfo);
/* Initialize the ring buffer. */
int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
@@ -269,14 +268,13 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
int hv_ringbuffer_write(struct vmbus_channel *channel,
const struct kvec *kv_list, u32 kv_count)
{
- int i = 0;
+ int i;
u32 bytes_avail_towrite;
- u32 totalbytes_towrite = 0;
-
+ u32 totalbytes_towrite = sizeof(u64);
u32 next_write_location;
u32 old_write;
- u64 prev_indices = 0;
- unsigned long flags = 0;
+ u64 prev_indices;
+ unsigned long flags;
struct hv_ring_buffer_info *outring_info = &channel->outbound;
if (channel->rescind)
@@ -285,8 +283,6 @@ int hv_ringbuffer_write(struct vmbus_channel *channel,
for (i = 0; i < kv_count; i++)
totalbytes_towrite += kv_list[i].iov_len;
- totalbytes_towrite += sizeof(u64);
-
spin_lock_irqsave(&outring_info->ring_lock, flags);
bytes_avail_towrite = hv_get_bytes_to_write(outring_info);
@@ -349,18 +345,16 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
u64 *requestid, bool raw)
{
u32 bytes_avail_toread;
- u32 next_read_location = 0;
+ u32 next_read_location;
u64 prev_indices = 0;
struct vmpacket_descriptor desc;
u32 offset;
u32 packetlen;
- int ret = 0;
struct hv_ring_buffer_info *inring_info = &channel->inbound;
if (buflen <= 0)
return -EINVAL;
-
*buffer_actual_len = 0;
*requestid = 0;
@@ -371,7 +365,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
* No error is set when there is even no header, drivers are
* supposed to analyze buffer_actual_len.
*/
- return ret;
+ return 0;
}
init_cached_read_index(inring_info);
@@ -417,7 +411,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
hv_signal_on_read(channel);
- return ret;
+ return 0;
}
/*
diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
index 8370b9dc6037..0087b49095eb 100644
--- a/drivers/hv/vmbus_drv.c
+++ b/drivers/hv/vmbus_drv.c
@@ -787,8 +787,6 @@ static void vmbus_shutdown(struct device *child_device)
if (drv->shutdown)
drv->shutdown(dev);
-
- return;
}
@@ -855,7 +853,7 @@ void vmbus_on_msg_dpc(unsigned long data)
struct hv_message *msg = (struct hv_message *)page_addr +
VMBUS_MESSAGE_SINT;
struct vmbus_channel_message_header *hdr;
- struct vmbus_channel_message_table_entry *entry;
+ const struct vmbus_channel_message_table_entry *entry;
struct onmessage_work_context *ctx;
u32 message_type = msg->header.message_type;
diff --git a/drivers/hwtracing/coresight/of_coresight.c b/drivers/hwtracing/coresight/of_coresight.c
index 629e031b7456..09142e99e915 100644
--- a/drivers/hwtracing/coresight/of_coresight.c
+++ b/drivers/hwtracing/coresight/of_coresight.c
@@ -149,7 +149,7 @@ struct coresight_platform_data *of_get_coresight_platform_data(
continue;
/* The local out port number */
- pdata->outports[i] = endpoint.id;
+ pdata->outports[i] = endpoint.port;
/*
* Get a handle on the remote port and parent
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 3ff91e02fee3..57c14da5708f 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -1719,18 +1719,13 @@ int iio_device_register(struct iio_dev *indio_dev)
cdev_init(&indio_dev->chrdev, &iio_buffer_fileops);
indio_dev->chrdev.owner = indio_dev->info->driver_module;
- indio_dev->chrdev.kobj.parent = &indio_dev->dev.kobj;
- ret = cdev_add(&indio_dev->chrdev, indio_dev->dev.devt, 1);
- if (ret < 0)
- goto error_unreg_eventset;
- ret = device_add(&indio_dev->dev);
+ ret = cdev_device_add(&indio_dev->chrdev, &indio_dev->dev);
if (ret < 0)
- goto error_cdev_del;
+ goto error_unreg_eventset;
return 0;
-error_cdev_del:
- cdev_del(&indio_dev->chrdev);
+
error_unreg_eventset:
iio_device_unregister_eventset(indio_dev);
error_free_sysfs:
@@ -1751,10 +1746,8 @@ void iio_device_unregister(struct iio_dev *indio_dev)
{
mutex_lock(&indio_dev->info_exist_lock);
- device_del(&indio_dev->dev);
+ cdev_device_del(&indio_dev->chrdev, &indio_dev->dev);
- if (indio_dev->chrdev.dev)
- cdev_del(&indio_dev->chrdev);
iio_device_unregister_debugfs(indio_dev);
iio_disable_all_buffers(indio_dev);
diff --git a/drivers/infiniband/core/ucm.c b/drivers/infiniband/core/ucm.c
index 80d0fca05c06..112099c86a19 100644
--- a/drivers/infiniband/core/ucm.c
+++ b/drivers/infiniband/core/ucm.c
@@ -1205,12 +1205,15 @@ static void ib_ucm_release_dev(struct device *dev)
struct ib_ucm_device *ucm_dev;
ucm_dev = container_of(dev, struct ib_ucm_device, dev);
- cdev_del(&ucm_dev->cdev);
+ kfree(ucm_dev);
+}
+
+static void ib_ucm_free_dev(struct ib_ucm_device *ucm_dev)
+{
if (ucm_dev->devnum < IB_UCM_MAX_DEVICES)
clear_bit(ucm_dev->devnum, dev_map);
else
clear_bit(ucm_dev->devnum - IB_UCM_MAX_DEVICES, overflow_map);
- kfree(ucm_dev);
}
static const struct file_operations ucm_fops = {
@@ -1266,7 +1269,9 @@ static void ib_ucm_add_one(struct ib_device *device)
if (!ucm_dev)
return;
+ device_initialize(&ucm_dev->dev);
ucm_dev->ib_dev = device;
+ ucm_dev->dev.release = ib_ucm_release_dev;
devnum = find_first_zero_bit(dev_map, IB_UCM_MAX_DEVICES);
if (devnum >= IB_UCM_MAX_DEVICES) {
@@ -1286,16 +1291,14 @@ static void ib_ucm_add_one(struct ib_device *device)
cdev_init(&ucm_dev->cdev, &ucm_fops);
ucm_dev->cdev.owner = THIS_MODULE;
kobject_set_name(&ucm_dev->cdev.kobj, "ucm%d", ucm_dev->devnum);
- if (cdev_add(&ucm_dev->cdev, base, 1))
- goto err;
ucm_dev->dev.class = &cm_class;
ucm_dev->dev.parent = device->dev.parent;
- ucm_dev->dev.devt = ucm_dev->cdev.dev;
- ucm_dev->dev.release = ib_ucm_release_dev;
+ ucm_dev->dev.devt = base;
+
dev_set_name(&ucm_dev->dev, "ucm%d", ucm_dev->devnum);
- if (device_register(&ucm_dev->dev))
- goto err_cdev;
+ if (cdev_device_add(&ucm_dev->cdev, &ucm_dev->dev))
+ goto err_devnum;
if (device_create_file(&ucm_dev->dev, &dev_attr_ibdev))
goto err_dev;
@@ -1304,15 +1307,11 @@ static void ib_ucm_add_one(struct ib_device *device)
return;
err_dev:
- device_unregister(&ucm_dev->dev);
-err_cdev:
- cdev_del(&ucm_dev->cdev);
- if (ucm_dev->devnum < IB_UCM_MAX_DEVICES)
- clear_bit(devnum, dev_map);
- else
- clear_bit(devnum, overflow_map);
+ cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev);
+err_devnum:
+ ib_ucm_free_dev(ucm_dev);
err:
- kfree(ucm_dev);
+ put_device(&ucm_dev->dev);
return;
}
@@ -1323,7 +1322,9 @@ static void ib_ucm_remove_one(struct ib_device *device, void *client_data)
if (!ucm_dev)
return;
- device_unregister(&ucm_dev->dev);
+ cdev_device_del(&ucm_dev->cdev, &ucm_dev->dev);
+ ib_ucm_free_dev(ucm_dev);
+ put_device(&ucm_dev->dev);
}
static CLASS_ATTR_STRING(abi_version, S_IRUGO,
diff --git a/drivers/infiniband/core/user_mad.c b/drivers/infiniband/core/user_mad.c
index 200422d24299..36a6f5c8914c 100644
--- a/drivers/infiniband/core/user_mad.c
+++ b/drivers/infiniband/core/user_mad.c
@@ -1187,7 +1187,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num,
cdev_init(&port->cdev, &umad_fops);
port->cdev.owner = THIS_MODULE;
- port->cdev.kobj.parent = &umad_dev->kobj;
+ cdev_set_parent(&port->cdev, &umad_dev->kobj);
kobject_set_name(&port->cdev.kobj, "umad%d", port->dev_num);
if (cdev_add(&port->cdev, base, 1))
goto err_cdev;
@@ -1206,7 +1206,7 @@ static int ib_umad_init_port(struct ib_device *device, int port_num,
base += IB_UMAD_MAX_PORTS;
cdev_init(&port->sm_cdev, &umad_sm_fops);
port->sm_cdev.owner = THIS_MODULE;
- port->sm_cdev.kobj.parent = &umad_dev->kobj;
+ cdev_set_parent(&port->sm_cdev, &umad_dev->kobj);
kobject_set_name(&port->sm_cdev.kobj, "issm%d", port->dev_num);
if (cdev_add(&port->sm_cdev, base, 1))
goto err_sm_cdev;
diff --git a/drivers/infiniband/core/uverbs_main.c b/drivers/infiniband/core/uverbs_main.c
index 3a9883d1257e..3d2609608f58 100644
--- a/drivers/infiniband/core/uverbs_main.c
+++ b/drivers/infiniband/core/uverbs_main.c
@@ -1093,7 +1093,7 @@ static void ib_uverbs_add_one(struct ib_device *device)
cdev_init(&uverbs_dev->cdev, NULL);
uverbs_dev->cdev.owner = THIS_MODULE;
uverbs_dev->cdev.ops = device->mmap ? &uverbs_mmap_fops : &uverbs_fops;
- uverbs_dev->cdev.kobj.parent = &uverbs_dev->kobj;
+ cdev_set_parent(&uverbs_dev->cdev, &uverbs_dev->kobj);
kobject_set_name(&uverbs_dev->cdev.kobj, "uverbs%d", uverbs_dev->devnum);
if (cdev_add(&uverbs_dev->cdev, base, 1))
goto err_cdev;
diff --git a/drivers/infiniband/hw/hfi1/device.c b/drivers/infiniband/hw/hfi1/device.c
index bf64b5a7bfd7..bbb6069dec2a 100644
--- a/drivers/infiniband/hw/hfi1/device.c
+++ b/drivers/infiniband/hw/hfi1/device.c
@@ -69,7 +69,7 @@ int hfi1_cdev_init(int minor, const char *name,
cdev_init(cdev, fops);
cdev->owner = THIS_MODULE;
- cdev->kobj.parent = parent;
+ cdev_set_parent(cdev, parent);
kobject_set_name(&cdev->kobj, name);
ret = cdev_add(cdev, dev, 1);
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index e9ae3d500a55..925571475005 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -1354,8 +1354,6 @@ static void evdev_cleanup(struct evdev *evdev)
evdev_mark_dead(evdev);
evdev_hangup(evdev);
- cdev_del(&evdev->cdev);
-
/* evdev is marked dead so no one else accesses evdev->open */
if (evdev->open) {
input_flush_device(handle, NULL);
@@ -1416,12 +1414,8 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
goto err_free_evdev;
cdev_init(&evdev->cdev, &evdev_fops);
- evdev->cdev.kobj.parent = &evdev->dev.kobj;
- error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
- if (error)
- goto err_unregister_handle;
- error = device_add(&evdev->dev);
+ error = cdev_device_add(&evdev->cdev, &evdev->dev);
if (error)
goto err_cleanup_evdev;
@@ -1429,7 +1423,6 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
err_cleanup_evdev:
evdev_cleanup(evdev);
- err_unregister_handle:
input_unregister_handle(&evdev->handle);
err_free_evdev:
put_device(&evdev->dev);
@@ -1442,7 +1435,7 @@ static void evdev_disconnect(struct input_handle *handle)
{
struct evdev *evdev = handle->private;
- device_del(&evdev->dev);
+ cdev_device_del(&evdev->cdev, &evdev->dev);
evdev_cleanup(evdev);
input_free_minor(MINOR(evdev->dev.devt));
input_unregister_handle(handle);
diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c
index 065e67bf56dd..29d677c714d2 100644
--- a/drivers/input/joydev.c
+++ b/drivers/input/joydev.c
@@ -742,8 +742,6 @@ static void joydev_cleanup(struct joydev *joydev)
joydev_mark_dead(joydev);
joydev_hangup(joydev);
- cdev_del(&joydev->cdev);
-
/* joydev is marked dead so no one else accesses joydev->open */
if (joydev->open)
input_close_device(handle);
@@ -913,12 +911,8 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev,
goto err_free_joydev;
cdev_init(&joydev->cdev, &joydev_fops);
- joydev->cdev.kobj.parent = &joydev->dev.kobj;
- error = cdev_add(&joydev->cdev, joydev->dev.devt, 1);
- if (error)
- goto err_unregister_handle;
- error = device_add(&joydev->dev);
+ error = cdev_device_add(&joydev->cdev, &joydev->dev);
if (error)
goto err_cleanup_joydev;
@@ -926,7 +920,6 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev,
err_cleanup_joydev:
joydev_cleanup(joydev);
- err_unregister_handle:
input_unregister_handle(&joydev->handle);
err_free_joydev:
put_device(&joydev->dev);
@@ -939,7 +932,7 @@ static void joydev_disconnect(struct input_handle *handle)
{
struct joydev *joydev = handle->private;
- device_del(&joydev->dev);
+ cdev_device_del(&joydev->cdev, &joydev->dev);
joydev_cleanup(joydev);
input_free_minor(MINOR(joydev->dev.devt));
input_unregister_handle(handle);
diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c
index b604564dec5c..0e0ff84088fd 100644
--- a/drivers/input/mousedev.c
+++ b/drivers/input/mousedev.c
@@ -812,8 +812,6 @@ static void mousedev_cleanup(struct mousedev *mousedev)
mousedev_mark_dead(mousedev);
mousedev_hangup(mousedev);
- cdev_del(&mousedev->cdev);
-
/* mousedev is marked dead so no one else accesses mousedev->open */
if (mousedev->open)
input_close_device(handle);
@@ -901,12 +899,8 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
}
cdev_init(&mousedev->cdev, &mousedev_fops);
- mousedev->cdev.kobj.parent = &mousedev->dev.kobj;
- error = cdev_add(&mousedev->cdev, mousedev->dev.devt, 1);
- if (error)
- goto err_unregister_handle;
- error = device_add(&mousedev->dev);
+ error = cdev_device_add(&mousedev->cdev, &mousedev->dev);
if (error)
goto err_cleanup_mousedev;
@@ -914,7 +908,6 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
err_cleanup_mousedev:
mousedev_cleanup(mousedev);
- err_unregister_handle:
if (!mixdev)
input_unregister_handle(&mousedev->handle);
err_free_mousedev:
@@ -927,7 +920,7 @@ static struct mousedev *mousedev_create(struct input_dev *dev,
static void mousedev_destroy(struct mousedev *mousedev)
{
- device_del(&mousedev->dev);
+ cdev_device_del(&mousedev->cdev, &mousedev->dev);
mousedev_cleanup(mousedev);
input_free_minor(MINOR(mousedev->dev.devt));
if (mousedev != mousedev_mix)
diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c
index 37217e205040..3163e038a364 100644
--- a/drivers/media/cec/cec-core.c
+++ b/drivers/media/cec/cec-core.c
@@ -137,24 +137,17 @@ static int __must_check cec_devnode_register(struct cec_devnode *devnode,
/* Part 2: Initialize and register the character device */
cdev_init(&devnode->cdev, &cec_devnode_fops);
- devnode->cdev.kobj.parent = &devnode->dev.kobj;
devnode->cdev.owner = owner;
- ret = cdev_add(&devnode->cdev, devnode->dev.devt, 1);
- if (ret < 0) {
- pr_err("%s: cdev_add failed\n", __func__);
+ ret = cdev_device_add(&devnode->cdev, &devnode->dev);
+ if (ret) {
+ pr_err("%s: cdev_device_add failed\n", __func__);
goto clr_bit;
}
- ret = device_add(&devnode->dev);
- if (ret)
- goto cdev_del;
-
devnode->registered = true;
return 0;
-cdev_del:
- cdev_del(&devnode->cdev);
clr_bit:
mutex_lock(&cec_devnode_lock);
clear_bit(devnode->minor, cec_devnode_nums);
@@ -190,8 +183,7 @@ static void cec_devnode_unregister(struct cec_devnode *devnode)
devnode->unregistered = true;
mutex_unlock(&devnode->lock);
- device_del(&devnode->dev);
- cdev_del(&devnode->cdev);
+ cdev_device_del(&devnode->cdev, &devnode->dev);
put_device(&devnode->dev);
}
diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c
index ae46753c90cb..423248f577b6 100644
--- a/drivers/media/media-devnode.c
+++ b/drivers/media/media-devnode.c
@@ -248,31 +248,22 @@ int __must_check media_devnode_register(struct media_device *mdev,
dev_set_name(&devnode->dev, "media%d", devnode->minor);
device_initialize(&devnode->dev);
- /* Part 2: Initialize and register the character device */
+ /* Part 2: Initialize the character device */
cdev_init(&devnode->cdev, &media_devnode_fops);
devnode->cdev.owner = owner;
- devnode->cdev.kobj.parent = &devnode->dev.kobj;
- ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1);
+ /* Part 3: Add the media and char device */
+ ret = cdev_device_add(&devnode->cdev, &devnode->dev);
if (ret < 0) {
- pr_err("%s: cdev_add failed\n", __func__);
+ pr_err("%s: cdev_device_add failed\n", __func__);
goto cdev_add_error;
}
- /* Part 3: Add the media device */
- ret = device_add(&devnode->dev);
- if (ret < 0) {
- pr_err("%s: device_add failed\n", __func__);
- goto device_add_error;
- }
-
/* Part 4: Activate this minor. The char device can now be used. */
set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
return 0;
-device_add_error:
- cdev_del(&devnode->cdev);
cdev_add_error:
mutex_lock(&media_devnode_lock);
clear_bit(devnode->minor, media_devnode_nums);
@@ -298,9 +289,8 @@ void media_devnode_unregister(struct media_devnode *devnode)
{
mutex_lock(&media_devnode_lock);
/* Delete the cdev on this minor as well */
- cdev_del(&devnode->cdev);
+ cdev_device_del(&devnode->cdev, &devnode->dev);
mutex_unlock(&media_devnode_lock);
- device_del(&devnode->dev);
devnode->media_dev = NULL;
put_device(&devnode->dev);
}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index c290990d73ed..39d1acb27452 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -419,16 +419,6 @@ config VMWARE_BALLOON
To compile this driver as a module, choose M here: the
module will be called vmw_balloon.
-config ARM_CHARLCD
- bool "ARM Ltd. Character LCD Driver"
- depends on PLAT_VERSATILE
- help
- This is a driver for the character LCD found on the ARM Ltd.
- Versatile and RealView Platform Baseboards. It doesn't do
- very much more than display the text "ARM Linux" on the first
- line and the Linux version on the second line, but that's
- still useful.
-
config PCH_PHUB
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
select GENERIC_NET_UTILS
@@ -492,284 +482,13 @@ config VEXPRESS_SYSCFG
bus. System Configuration interface is one of the possible means
of generating transactions on this bus.
-config PANEL
- tristate "Parallel port LCD/Keypad Panel support"
- depends on PARPORT
- ---help---
- Say Y here if you have an HD44780 or KS-0074 LCD connected to your
- parallel port. This driver also features 4 and 6-key keypads. The LCD
- is accessible through the /dev/lcd char device (10, 156), and the
- keypad through /dev/keypad (10, 185). This code can either be
- compiled as a module, or linked into the kernel and started at boot.
- If you don't understand what all this is about, say N.
-
-if PANEL
-
-config PANEL_PARPORT
- int "Default parallel port number (0=LPT1)"
- range 0 255
- default "0"
- ---help---
- This is the index of the parallel port the panel is connected to. One
- driver instance only supports one parallel port, so if your keypad
- and LCD are connected to two separate ports, you have to start two
- modules with different arguments. Numbering starts with '0' for LPT1,
- and so on.
-
-config PANEL_PROFILE
- int "Default panel profile (0-5, 0=custom)"
- range 0 5
- default "5"
- ---help---
- To ease configuration, the driver supports different configuration
- profiles for past and recent wirings. These profiles can also be
- used to define an approximative configuration, completed by a few
- other options. Here are the profiles :
-
- 0 = custom (see further)
- 1 = 2x16 parallel LCD, old keypad
- 2 = 2x16 serial LCD (KS-0074), new keypad
- 3 = 2x16 parallel LCD (Hantronix), no keypad
- 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad
- 5 = 2x40 parallel LCD (old one), with old keypad
-
- Custom configurations allow you to define how your display is
- wired to the parallel port, and how it works. This is only intended
- for experts.
-
-config PANEL_KEYPAD
- depends on PANEL_PROFILE="0"
- int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)"
- range 0 3
- default 0
- ---help---
- This enables and configures a keypad connected to the parallel port.
- The keys will be read from character device 10,185. Valid values are :
-
- 0 : do not enable this driver
- 1 : old 6 keys keypad
- 2 : new 6 keys keypad, as used on the server at www.ant-computing.com
- 3 : Nexcom NSA1045's 4 keys keypad
-
- New profiles can be described in the driver source. The driver also
- supports simultaneous keys pressed when the keypad supports them.
-
-config PANEL_LCD
- depends on PANEL_PROFILE="0"
- int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)"
- range 0 5
- default 0
- ---help---
- This enables and configures an LCD connected to the parallel port.
- The driver includes an interpreter for escape codes starting with
- '\e[L' which are specific to the LCD, and a few ANSI codes. The
- driver will be registered as character device 10,156, usually
- under the name '/dev/lcd'. There are a total of 6 supported types :
-
- 0 : do not enable the driver
- 1 : custom configuration and wiring (see further)
- 2 : 2x16 & 2x40 parallel LCD (old wiring)
- 3 : 2x16 serial LCD (KS-0074 based)
- 4 : 2x16 parallel LCD (Hantronix wiring)
- 5 : 2x16 parallel LCD (Nexcom wiring)
-
- When type '1' is specified, other options will appear to configure
- more precise aspects (wiring, dimensions, protocol, ...). Please note
- that those values changed from the 2.4 driver for better consistency.
-
-config PANEL_LCD_HEIGHT
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "Number of lines on the LCD (1-2)"
- range 1 2
- default 2
- ---help---
- This is the number of visible character lines on the LCD in custom profile.
- It can either be 1 or 2.
-
-config PANEL_LCD_WIDTH
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "Number of characters per line on the LCD (1-40)"
- range 1 40
- default 40
- ---help---
- This is the number of characters per line on the LCD in custom profile.
- Common values are 16,20,24,40.
-
-config PANEL_LCD_BWIDTH
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "Internal LCD line width (1-40, 40 by default)"
- range 1 40
- default 40
- ---help---
- Most LCDs use a standard controller which supports hardware lines of 40
- characters, although sometimes only 16, 20 or 24 of them are really wired
- to the terminal. This results in some non-visible but addressable characters,
- and is the case for most parallel LCDs. Other LCDs, and some serial ones,
- however, use the same line width internally as what is visible. The KS0074
- for example, uses 16 characters per line for 16 visible characters per line.
-
- This option lets you configure the value used by your LCD in 'custom' profile.
- If you don't know, put '40' here.
-
-config PANEL_LCD_HWIDTH
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "Hardware LCD line width (1-64, 64 by default)"
- range 1 64
- default 64
- ---help---
- Most LCDs use a single address bit to differentiate line 0 and line 1. Since
- some of them need to be able to address 40 chars with the lower bits, they
- often use the immediately superior power of 2, which is 64, to address the
- next line.
-
- If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and
- 64 here for a 2x40.
-
-config PANEL_LCD_CHARSET
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "LCD character set (0=normal, 1=KS0074)"
- range 0 1
- default 0
- ---help---
- Some controllers such as the KS0074 use a somewhat strange character set
- where many symbols are at unusual places. The driver knows how to map
- 'standard' ASCII characters to the character sets used by these controllers.
- Valid values are :
-
- 0 : normal (untranslated) character set
- 1 : KS0074 character set
-
- If you don't know, use the normal one (0).
-
-config PANEL_LCD_PROTO
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "LCD communication mode (0=parallel 8 bits, 1=serial)"
- range 0 1
- default 0
- ---help---
- This driver now supports any serial or parallel LCD wired to a parallel
- port. But before assigning signals, the driver needs to know if it will
- be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires
- (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals
- (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits
- parallel LCD, and 1 for a serial LCD.
-
-config PANEL_LCD_PIN_E
- depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
- int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) "
- range -17 17
- default 14
+config ASPEED_LPC_CTRL
+ depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON
+ tristate "Aspeed ast2400/2500 HOST LPC to BMC bridge control"
---help---
- This describes the number of the parallel port pin to which the LCD 'E'
- signal has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'E' pin in custom profile is '14' (AUTOFEED).
-
-config PANEL_LCD_PIN_RS
- depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
- int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) "
- range -17 17
- default 17
- ---help---
- This describes the number of the parallel port pin to which the LCD 'RS'
- signal has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'RS' pin in custom profile is '17' (SELECT IN).
-
-config PANEL_LCD_PIN_RW
- depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0"
- int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) "
- range -17 17
- default 16
- ---help---
- This describes the number of the parallel port pin to which the LCD 'RW'
- signal has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'RW' pin in custom profile is '16' (INIT).
-
-config PANEL_LCD_PIN_SCL
- depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
- int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) "
- range -17 17
- default 1
- ---help---
- This describes the number of the parallel port pin to which the serial
- LCD 'SCL' signal has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'SCL' pin in custom profile is '1' (STROBE).
-
-config PANEL_LCD_PIN_SDA
- depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0"
- int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) "
- range -17 17
- default 2
- ---help---
- This describes the number of the parallel port pin to which the serial
- LCD 'SDA' signal has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'SDA' pin in custom profile is '2' (D0).
-
-config PANEL_LCD_PIN_BL
- depends on PANEL_PROFILE="0" && PANEL_LCD="1"
- int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) "
- range -17 17
- default 0
- ---help---
- This describes the number of the parallel port pin to which the LCD 'BL' signal
- has been connected. It can be :
-
- 0 : no connection (eg: connected to ground)
- 1..17 : directly connected to any of these pins on the DB25 plug
- -1..-17 : connected to the same pin through an inverter (eg: transistor).
-
- Default for the 'BL' pin in custom profile is '0' (uncontrolled).
-
-config PANEL_CHANGE_MESSAGE
- bool "Change LCD initialization message ?"
- default "n"
- ---help---
- This allows you to replace the boot message indicating the kernel version
- and the driver version with a custom message. This is useful on appliances
- where a simple 'Starting system' message can be enough to stop a customer
- from worrying.
-
- If you say 'Y' here, you'll be able to choose a message yourself. Otherwise,
- say 'N' and keep the default message with the version.
-
-config PANEL_BOOT_MESSAGE
- depends on PANEL_CHANGE_MESSAGE="y"
- string "New initialization message"
- default ""
- ---help---
- This allows you to replace the boot message indicating the kernel version
- and the driver version with a custom message. This is useful on appliances
- where a simple 'Starting system' message can be enough to stop a customer
- from worrying.
-
- An empty message will only clear the display at driver init time. Any other
- printf()-formatted message is valid with newline and escape codes.
-
-endif # PANEL
+ Control Aspeed ast2400/2500 HOST LPC to BMC mappings through
+ ioctl()s, the driver also provides a read/write interface to a BMC ram
+ region where the host LPC read/write region can be buffered.
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 7a3ea89339b4..4fb10af2ea1c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -37,7 +37,6 @@ obj-y += eeprom/
obj-y += cb710/
obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
-obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
obj-y += ti-st/
obj-y += lis3lv02d/
@@ -53,7 +52,7 @@ obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
-obj-$(CONFIG_PANEL) += panel.o
+obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
@@ -62,6 +61,8 @@ lkdtm-$(CONFIG_LKDTM) += lkdtm_perms.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_rodata_objcopy.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_usercopy.o
+KCOV_INSTRUMENT_lkdtm_rodata.o := n
+
OBJCOPYFLAGS :=
OBJCOPYFLAGS_lkdtm_rodata_objcopy.o := \
--set-section-flags .text=alloc,readonly \
diff --git a/drivers/misc/aspeed-lpc-ctrl.c b/drivers/misc/aspeed-lpc-ctrl.c
new file mode 100644
index 000000000000..b5439643f54b
--- /dev/null
+++ b/drivers/misc/aspeed-lpc-ctrl.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2017 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/regmap.h>
+
+#include <linux/aspeed-lpc-ctrl.h>
+
+#define DEVICE_NAME "aspeed-lpc-ctrl"
+
+#define HICR7 0x8
+#define HICR8 0xc
+
+struct aspeed_lpc_ctrl {
+ struct miscdevice miscdev;
+ struct regmap *regmap;
+ phys_addr_t mem_base;
+ resource_size_t mem_size;
+ u32 pnor_size;
+ u32 pnor_base;
+};
+
+static struct aspeed_lpc_ctrl *file_aspeed_lpc_ctrl(struct file *file)
+{
+ return container_of(file->private_data, struct aspeed_lpc_ctrl,
+ miscdev);
+}
+
+static int aspeed_lpc_ctrl_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+ unsigned long vsize = vma->vm_end - vma->vm_start;
+ pgprot_t prot = vma->vm_page_prot;
+
+ if (vma->vm_pgoff + vsize > lpc_ctrl->mem_base + lpc_ctrl->mem_size)
+ return -EINVAL;
+
+ /* ast2400/2500 AHB accesses are not cache coherent */
+ prot = pgprot_noncached(prot);
+
+ if (remap_pfn_range(vma, vma->vm_start,
+ (lpc_ctrl->mem_base >> PAGE_SHIFT) + vma->vm_pgoff,
+ vsize, prot))
+ return -EAGAIN;
+
+ return 0;
+}
+
+static long aspeed_lpc_ctrl_ioctl(struct file *file, unsigned int cmd,
+ unsigned long param)
+{
+ struct aspeed_lpc_ctrl *lpc_ctrl = file_aspeed_lpc_ctrl(file);
+ void __user *p = (void __user *)param;
+ struct aspeed_lpc_ctrl_mapping map;
+ u32 addr;
+ u32 size;
+ long rc;
+
+ if (copy_from_user(&map, p, sizeof(map)))
+ return -EFAULT;
+
+ if (map.flags != 0)
+ return -EINVAL;
+
+ switch (cmd) {
+ case ASPEED_LPC_CTRL_IOCTL_GET_SIZE:
+ /* The flash windows don't report their size */
+ if (map.window_type != ASPEED_LPC_CTRL_WINDOW_MEMORY)
+ return -EINVAL;
+
+ /* Support more than one window id in the future */
+ if (map.window_id != 0)
+ return -EINVAL;
+
+ map.size = lpc_ctrl->mem_size;
+
+ return copy_to_user(p, &map, sizeof(map)) ? -EFAULT : 0;
+ case ASPEED_LPC_CTRL_IOCTL_MAP:
+
+ /*
+ * The top half of HICR7 is the MSB of the BMC address of the
+ * mapping.
+ * The bottom half of HICR7 is the MSB of the HOST LPC
+ * firmware space address of the mapping.
+ *
+ * The 1 bits in the top of half of HICR8 represent the bits
+ * (in the requested address) that should be ignored and
+ * replaced with those from the top half of HICR7.
+ * The 1 bits in the bottom half of HICR8 represent the bits
+ * (in the requested address) that should be kept and pass
+ * into the BMC address space.
+ */
+
+ /*
+ * It doesn't make sense to talk about a size or offset with
+ * low 16 bits set. Both HICR7 and HICR8 talk about the top 16
+ * bits of addresses and sizes.
+ */
+
+ if ((map.size & 0x0000ffff) || (map.offset & 0x0000ffff))
+ return -EINVAL;
+
+ /*
+ * Because of the way the masks work in HICR8 offset has to
+ * be a multiple of size.
+ */
+ if (map.offset & (map.size - 1))
+ return -EINVAL;
+
+ if (map.window_type == ASPEED_LPC_CTRL_WINDOW_FLASH) {
+ addr = lpc_ctrl->pnor_base;
+ size = lpc_ctrl->pnor_size;
+ } else if (map.window_type == ASPEED_LPC_CTRL_WINDOW_MEMORY) {
+ addr = lpc_ctrl->mem_base;
+ size = lpc_ctrl->mem_size;
+ } else {
+ return -EINVAL;
+ }
+
+ /* Check overflow first! */
+ if (map.offset + map.size < map.offset ||
+ map.offset + map.size > size)
+ return -EINVAL;
+
+ if (map.size == 0 || map.size > size)
+ return -EINVAL;
+
+ addr += map.offset;
+
+ /*
+ * addr (host lpc address) is safe regardless of values. This
+ * simply changes the address the host has to request on its
+ * side of the LPC bus. This cannot impact the hosts own
+ * memory space by surprise as LPC specific accessors are
+ * required. The only strange thing that could be done is
+ * setting the lower 16 bits but the shift takes care of that.
+ */
+
+ rc = regmap_write(lpc_ctrl->regmap, HICR7,
+ (addr | (map.addr >> 16)));
+ if (rc)
+ return rc;
+
+ return regmap_write(lpc_ctrl->regmap, HICR8,
+ (~(map.size - 1)) | ((map.size >> 16) - 1));
+ }
+
+ return -EINVAL;
+}
+
+static const struct file_operations aspeed_lpc_ctrl_fops = {
+ .owner = THIS_MODULE,
+ .mmap = aspeed_lpc_ctrl_mmap,
+ .unlocked_ioctl = aspeed_lpc_ctrl_ioctl,
+};
+
+static int aspeed_lpc_ctrl_probe(struct platform_device *pdev)
+{
+ struct aspeed_lpc_ctrl *lpc_ctrl;
+ struct device_node *node;
+ struct resource resm;
+ struct device *dev;
+ int rc;
+
+ dev = &pdev->dev;
+
+ lpc_ctrl = devm_kzalloc(dev, sizeof(*lpc_ctrl), GFP_KERNEL);
+ if (!lpc_ctrl)
+ return -ENOMEM;
+
+ node = of_parse_phandle(dev->of_node, "flash", 0);
+ if (!node) {
+ dev_err(dev, "Didn't find host pnor flash node\n");
+ return -ENODEV;
+ }
+
+ rc = of_address_to_resource(node, 1, &resm);
+ of_node_put(node);
+ if (rc) {
+ dev_err(dev, "Couldn't address to resource for flash\n");
+ return rc;
+ }
+
+ lpc_ctrl->pnor_size = resource_size(&resm);
+ lpc_ctrl->pnor_base = resm.start;
+
+ dev_set_drvdata(&pdev->dev, lpc_ctrl);
+
+ node = of_parse_phandle(dev->of_node, "memory-region", 0);
+ if (!node) {
+ dev_err(dev, "Didn't find reserved memory\n");
+ return -EINVAL;
+ }
+
+ rc = of_address_to_resource(node, 0, &resm);
+ of_node_put(node);
+ if (rc) {
+ dev_err(dev, "Couldn't address to resource for reserved memory\n");
+ return -ENOMEM;
+ }
+
+ lpc_ctrl->mem_size = resource_size(&resm);
+ lpc_ctrl->mem_base = resm.start;
+
+ lpc_ctrl->regmap = syscon_node_to_regmap(
+ pdev->dev.parent->of_node);
+ if (IS_ERR(lpc_ctrl->regmap)) {
+ dev_err(dev, "Couldn't get regmap\n");
+ return -ENODEV;
+ }
+
+ lpc_ctrl->miscdev.minor = MISC_DYNAMIC_MINOR;
+ lpc_ctrl->miscdev.name = DEVICE_NAME;
+ lpc_ctrl->miscdev.fops = &aspeed_lpc_ctrl_fops;
+ lpc_ctrl->miscdev.parent = dev;
+ rc = misc_register(&lpc_ctrl->miscdev);
+ if (rc)
+ dev_err(dev, "Unable to register device\n");
+ else
+ dev_info(dev, "Loaded at %pr\n", &resm);
+
+ return rc;
+}
+
+static int aspeed_lpc_ctrl_remove(struct platform_device *pdev)
+{
+ struct aspeed_lpc_ctrl *lpc_ctrl = dev_get_drvdata(&pdev->dev);
+
+ misc_deregister(&lpc_ctrl->miscdev);
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_lpc_ctrl_match[] = {
+ { .compatible = "aspeed,ast2400-lpc-ctrl" },
+ { .compatible = "aspeed,ast2500-lpc-ctrl" },
+ { },
+};
+
+static struct platform_driver aspeed_lpc_ctrl_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ .of_match_table = aspeed_lpc_ctrl_match,
+ },
+ .probe = aspeed_lpc_ctrl_probe,
+ .remove = aspeed_lpc_ctrl_remove,
+};
+
+module_platform_driver(aspeed_lpc_ctrl_driver);
+
+MODULE_DEVICE_TABLE(of, aspeed_lpc_ctrl_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>");
+MODULE_DESCRIPTION("Control for aspeed 2400/2500 LPC HOST to BMC mappings");
diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c
index c7112276a039..28bb495f0cf1 100644
--- a/drivers/misc/ds1682.c
+++ b/drivers/misc/ds1682.c
@@ -227,9 +227,16 @@ static const struct i2c_device_id ds1682_id[] = {
};
MODULE_DEVICE_TABLE(i2c, ds1682_id);
+static const struct of_device_id ds1682_of_match[] = {
+ { .compatible = "dallas,ds1682", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ds1682_of_match);
+
static struct i2c_driver ds1682_driver = {
.driver = {
.name = "ds1682",
+ .of_match_table = ds1682_of_match,
},
.probe = ds1682_probe,
.remove = ds1682_remove,
diff --git a/drivers/misc/eeprom/idt_89hpesx.c b/drivers/misc/eeprom/idt_89hpesx.c
index 4a22a1d99395..ab0df6a17690 100644
--- a/drivers/misc/eeprom/idt_89hpesx.c
+++ b/drivers/misc/eeprom/idt_89hpesx.c
@@ -1541,12 +1541,69 @@ static const struct i2c_device_id idt_ids[] = {
};
MODULE_DEVICE_TABLE(i2c, idt_ids);
+static const struct of_device_id idt_of_match[] = {
+ { .compatible = "idt,89hpes8nt2", },
+ { .compatible = "idt,89hpes12nt3", },
+
+ { .compatible = "idt,89hpes24nt6ag2", },
+ { .compatible = "idt,89hpes32nt8ag2", },
+ { .compatible = "idt,89hpes32nt8bg2", },
+ { .compatible = "idt,89hpes12nt12g2", },
+ { .compatible = "idt,89hpes16nt16g2", },
+ { .compatible = "idt,89hpes24nt24g2", },
+ { .compatible = "idt,89hpes32nt24ag2", },
+ { .compatible = "idt,89hpes32nt24bg2", },
+
+ { .compatible = "idt,89hpes12n3", },
+ { .compatible = "idt,89hpes12n3a", },
+ { .compatible = "idt,89hpes24n3", },
+ { .compatible = "idt,89hpes24n3a", },
+
+ { .compatible = "idt,89hpes32h8", },
+ { .compatible = "idt,89hpes32h8g2", },
+ { .compatible = "idt,89hpes48h12", },
+ { .compatible = "idt,89hpes48h12g2", },
+ { .compatible = "idt,89hpes48h12ag2", },
+ { .compatible = "idt,89hpes16h16", },
+ { .compatible = "idt,89hpes22h16", },
+ { .compatible = "idt,89hpes22h16g2", },
+ { .compatible = "idt,89hpes34h16", },
+ { .compatible = "idt,89hpes34h16g2", },
+ { .compatible = "idt,89hpes64h16", },
+ { .compatible = "idt,89hpes64h16g2", },
+ { .compatible = "idt,89hpes64h16ag2", },
+
+ { .compatible = "idt,89hpes12t3g2", },
+ { .compatible = "idt,89hpes24t3g2", },
+
+ { .compatible = "idt,89hpes16t4", },
+ { .compatible = "idt,89hpes4t4g2", },
+ { .compatible = "idt,89hpes10t4g2", },
+ { .compatible = "idt,89hpes16t4g2", },
+ { .compatible = "idt,89hpes16t4ag2", },
+ { .compatible = "idt,89hpes5t5", },
+ { .compatible = "idt,89hpes6t5", },
+ { .compatible = "idt,89hpes8t5", },
+ { .compatible = "idt,89hpes8t5a", },
+ { .compatible = "idt,89hpes24t6", },
+ { .compatible = "idt,89hpes6t6g2", },
+ { .compatible = "idt,89hpes24t6g2", },
+ { .compatible = "idt,89hpes16t7", },
+ { .compatible = "idt,89hpes32t8", },
+ { .compatible = "idt,89hpes32t8g2", },
+ { .compatible = "idt,89hpes48t12", },
+ { .compatible = "idt,89hpes48t12g2", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, idt_of_match);
+
/*
* idt_driver - IDT 89HPESx driver structure
*/
static struct i2c_driver idt_driver = {
.driver = {
.name = IDT_NAME,
+ .of_match_table = idt_of_match,
},
.probe = idt_probe,
.remove = idt_remove,
diff --git a/drivers/misc/lkdtm.h b/drivers/misc/lkdtm.h
index 67d27be60405..3b4976396ec4 100644
--- a/drivers/misc/lkdtm.h
+++ b/drivers/misc/lkdtm.h
@@ -27,6 +27,7 @@ void lkdtm_REFCOUNT_ZERO_SUB(void);
void lkdtm_REFCOUNT_ZERO_ADD(void);
void lkdtm_CORRUPT_LIST_ADD(void);
void lkdtm_CORRUPT_LIST_DEL(void);
+void lkdtm_CORRUPT_USER_DS(void);
/* lkdtm_heap.c */
void lkdtm_OVERWRITE_ALLOCATION(void);
diff --git a/drivers/misc/lkdtm_bugs.c b/drivers/misc/lkdtm_bugs.c
index e3f4cd8876b5..d9028ef50fbe 100644
--- a/drivers/misc/lkdtm_bugs.c
+++ b/drivers/misc/lkdtm_bugs.c
@@ -8,6 +8,8 @@
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
struct lkdtm_list {
struct list_head node;
@@ -67,7 +69,7 @@ void lkdtm_WARNING(void)
void lkdtm_EXCEPTION(void)
{
- *((int *) 0) = 0;
+ *((volatile int *) 0) = 0;
}
void lkdtm_LOOP(void)
@@ -279,3 +281,12 @@ void lkdtm_CORRUPT_LIST_DEL(void)
else
pr_err("list_del() corruption not detected!\n");
}
+
+void lkdtm_CORRUPT_USER_DS(void)
+{
+ pr_info("setting bad task size limit\n");
+ set_fs(KERNEL_DS);
+
+ /* Make sure we do not keep running with a KERNEL_DS! */
+ force_sig(SIGKILL, current);
+}
diff --git a/drivers/misc/lkdtm_core.c b/drivers/misc/lkdtm_core.c
index b9a4cd4a9b68..42d2b8e31e6b 100644
--- a/drivers/misc/lkdtm_core.c
+++ b/drivers/misc/lkdtm_core.c
@@ -199,6 +199,7 @@ struct crashtype crashtypes[] = {
CRASHTYPE(OVERFLOW),
CRASHTYPE(CORRUPT_LIST_ADD),
CRASHTYPE(CORRUPT_LIST_DEL),
+ CRASHTYPE(CORRUPT_USER_DS),
CRASHTYPE(CORRUPT_STACK),
CRASHTYPE(UNALIGNED_LOAD_STORE_WRITE),
CRASHTYPE(OVERWRITE_ALLOCATION),
diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile
index 59e6b0aede34..12cceb011a23 100644
--- a/drivers/misc/mei/Makefile
+++ b/drivers/misc/mei/Makefile
@@ -8,7 +8,6 @@ mei-objs += hbm.o
mei-objs += interrupt.o
mei-objs += client.o
mei-objs += main.o
-mei-objs += amthif.o
mei-objs += bus.o
mei-objs += bus-fixup.o
mei-$(CONFIG_DEBUG_FS) += debugfs.o
diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c
deleted file mode 100644
index 0e7406ccb6dd..000000000000
--- a/drivers/misc/mei/amthif.c
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- *
- * Intel Management Engine Interface (Intel MEI) Linux driver
- * Copyright (c) 2003-2012, Intel Corporation.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- */
-
-#include <linux/kernel.h>
-#include <linux/fs.h>
-#include <linux/errno.h>
-#include <linux/types.h>
-#include <linux/fcntl.h>
-#include <linux/ioctl.h>
-#include <linux/cdev.h>
-#include <linux/list.h>
-#include <linux/delay.h>
-#include <linux/sched.h>
-#include <linux/uuid.h>
-#include <linux/jiffies.h>
-#include <linux/uaccess.h>
-#include <linux/slab.h>
-
-#include <linux/mei.h>
-
-#include "mei_dev.h"
-#include "hbm.h"
-#include "client.h"
-
-const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d,
- 0xac, 0xa8, 0x46, 0xe0,
- 0xff, 0x65, 0x81, 0x4c);
-
-/**
- * mei_amthif_reset_params - initializes mei device iamthif
- *
- * @dev: the device structure
- */
-void mei_amthif_reset_params(struct mei_device *dev)
-{
- /* reset iamthif parameters. */
- dev->iamthif_canceled = false;
- dev->iamthif_state = MEI_IAMTHIF_IDLE;
- dev->iamthif_stall_timer = 0;
- dev->iamthif_open_count = 0;
-}
-
-/**
- * mei_amthif_host_init - mei initialization amthif client.
- *
- * @dev: the device structure
- * @me_cl: me client
- *
- * Return: 0 on success, <0 on failure.
- */
-int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl)
-{
- struct mei_cl *cl = &dev->iamthif_cl;
- int ret;
-
- mutex_lock(&dev->device_lock);
-
- if (mei_cl_is_connected(cl)) {
- ret = 0;
- goto out;
- }
-
- dev->iamthif_state = MEI_IAMTHIF_IDLE;
-
- mei_cl_init(cl, dev);
-
- ret = mei_cl_link(cl);
- if (ret < 0) {
- dev_err(dev->dev, "amthif: failed cl_link %d\n", ret);
- goto out;
- }
-
- ret = mei_cl_connect(cl, me_cl, NULL);
-
-out:
- mutex_unlock(&dev->device_lock);
- return ret;
-}
-
-/**
- * mei_amthif_read_start - queue message for sending read credential
- *
- * @cl: host client
- * @fp: file pointer of message recipient
- *
- * Return: 0 on success, <0 on failure.
- */
-static int mei_amthif_read_start(struct mei_cl *cl, const struct file *fp)
-{
- struct mei_device *dev = cl->dev;
- struct mei_cl_cb *cb;
-
- cb = mei_cl_enqueue_ctrl_wr_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, fp);
- if (!cb)
- return -ENOMEM;
-
- cl->rx_flow_ctrl_creds++;
-
- dev->iamthif_state = MEI_IAMTHIF_READING;
- cl->fp = cb->fp;
-
- return 0;
-}
-
-/**
- * mei_amthif_run_next_cmd - send next amt command from queue
- *
- * @dev: the device structure
- *
- * Return: 0 on success, <0 on failure.
- */
-int mei_amthif_run_next_cmd(struct mei_device *dev)
-{
- struct mei_cl *cl = &dev->iamthif_cl;
- struct mei_cl_cb *cb;
- int ret;
-
- dev->iamthif_canceled = false;
-
- dev_dbg(dev->dev, "complete amthif cmd_list cb.\n");
-
- cb = list_first_entry_or_null(&dev->amthif_cmd_list, typeof(*cb), list);
- if (!cb) {
- dev->iamthif_state = MEI_IAMTHIF_IDLE;
- cl->fp = NULL;
- return 0;
- }
-
- list_del_init(&cb->list);
- dev->iamthif_state = MEI_IAMTHIF_WRITING;
- cl->fp = cb->fp;
-
- ret = mei_cl_write(cl, cb);
- if (ret < 0)
- return ret;
-
- if (cb->completed)
- cb->status = mei_amthif_read_start(cl, cb->fp);
-
- return 0;
-}
-
-/**
- * mei_amthif_write - write amthif data to amthif client
- *
- * @cl: host client
- * @cb: mei call back struct
- *
- * Return: 0 on success, <0 on failure.
- */
-int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb)
-{
-
- struct mei_device *dev = cl->dev;
-
- list_add_tail(&cb->list, &dev->amthif_cmd_list);
-
- /*
- * The previous request is still in processing, queue this one.
- */
- if (dev->iamthif_state != MEI_IAMTHIF_IDLE)
- return 0;
-
- return mei_amthif_run_next_cmd(dev);
-}
-
-/**
- * mei_amthif_poll - the amthif poll function
- *
- * @file: pointer to file structure
- * @wait: pointer to poll_table structure
- *
- * Return: poll mask
- *
- * Locking: called under "dev->device_lock" lock
- */
-unsigned int mei_amthif_poll(struct file *file, poll_table *wait)
-{
- struct mei_cl *cl = file->private_data;
- struct mei_cl_cb *cb = mei_cl_read_cb(cl, file);
- unsigned int mask = 0;
-
- poll_wait(file, &cl->rx_wait, wait);
- if (cb)
- mask |= POLLIN | POLLRDNORM;
-
- return mask;
-}
-
-/**
- * mei_amthif_irq_write - write iamthif command in irq thread context.
- *
- * @cl: private data of the file object.
- * @cb: callback block.
- * @cmpl_list: complete list.
- *
- * Return: 0, OK; otherwise, error.
- */
-int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
- struct list_head *cmpl_list)
-{
- int ret;
-
- ret = mei_cl_irq_write(cl, cb, cmpl_list);
- if (ret)
- return ret;
-
- if (cb->completed)
- cb->status = mei_amthif_read_start(cl, cb->fp);
-
- return 0;
-}
-
-/**
- * mei_amthif_irq_read_msg - read routine after ISR to
- * handle the read amthif message
- *
- * @cl: mei client
- * @mei_hdr: header of amthif message
- * @cmpl_list: completed callbacks list
- *
- * Return: -ENODEV if cb is NULL 0 otherwise; error message is in cb->status
- */
-int mei_amthif_irq_read_msg(struct mei_cl *cl,
- struct mei_msg_hdr *mei_hdr,
- struct list_head *cmpl_list)
-{
- struct mei_device *dev;
- int ret;
-
- dev = cl->dev;
-
- if (dev->iamthif_state != MEI_IAMTHIF_READING) {
- mei_irq_discard_msg(dev, mei_hdr);
- return 0;
- }
-
- ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list);
- if (ret)
- return ret;
-
- if (!mei_hdr->msg_complete)
- return 0;
-
- dev_dbg(dev->dev, "completed amthif read.\n ");
- dev->iamthif_stall_timer = 0;
-
- return 0;
-}
-
-/**
- * mei_amthif_complete - complete amthif callback.
- *
- * @cl: host client
- * @cb: callback block.
- */
-void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb)
-{
- struct mei_device *dev = cl->dev;
-
- dev_dbg(dev->dev, "completing amthif call back.\n");
- switch (cb->fop_type) {
- case MEI_FOP_WRITE:
- if (!cb->status) {
- dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER;
- mei_schedule_stall_timer(dev);
- mei_io_cb_free(cb);
- return;
- }
- dev->iamthif_state = MEI_IAMTHIF_IDLE;
- cl->fp = NULL;
- if (!dev->iamthif_canceled) {
- /*
- * in case of error enqueue the write cb to complete
- * read list so it can be propagated to the reader
- */
- list_add_tail(&cb->list, &cl->rd_completed);
- wake_up_interruptible(&cl->rx_wait);
- } else {
- mei_io_cb_free(cb);
- }
- break;
- case MEI_FOP_READ:
- if (!dev->iamthif_canceled) {
- list_add_tail(&cb->list, &cl->rd_completed);
- dev_dbg(dev->dev, "amthif read completed\n");
- wake_up_interruptible(&cl->rx_wait);
- } else {
- mei_io_cb_free(cb);
- }
-
- dev->iamthif_stall_timer = 0;
- mei_amthif_run_next_cmd(dev);
- break;
- default:
- WARN_ON(1);
- }
-}
-
-/**
-* mei_amthif_release - the release function
-*
-* @dev: device structure
-* @fp: pointer to file structure
-*
-* Return: 0 on success, <0 on error
-*/
-int mei_amthif_release(struct mei_device *dev, struct file *fp)
-{
- struct mei_cl *cl = fp->private_data;
-
- if (dev->iamthif_open_count > 0)
- dev->iamthif_open_count--;
-
- if (cl->fp == fp && dev->iamthif_state != MEI_IAMTHIF_IDLE) {
-
- dev_dbg(dev->dev, "amthif canceled iamthif state %d\n",
- dev->iamthif_state);
- dev->iamthif_canceled = true;
- }
-
- /* Don't clean ctrl_rd_list here, the reads has to be completed */
- mei_io_list_free_fp(&dev->amthif_cmd_list, fp);
- mei_io_list_free_fp(&cl->rd_completed, fp);
-
- return 0;
-}
diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c
index 29f2daed37e0..0208c4b027c5 100644
--- a/drivers/misc/mei/bus-fixup.c
+++ b/drivers/misc/mei/bus-fixup.c
@@ -110,12 +110,13 @@ struct mkhi_msg {
u8 data[0];
} __packed;
+#define MKHI_OSVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \
+ sizeof(struct mkhi_fwcaps) + \
+ sizeof(struct mei_os_ver))
static int mei_osver(struct mei_cl_device *cldev)
{
- const size_t size = sizeof(struct mkhi_msg_hdr) +
- sizeof(struct mkhi_fwcaps) +
- sizeof(struct mei_os_ver);
- char buf[size];
+ const size_t size = MKHI_OSVER_BUF_LEN;
+ char buf[MKHI_OSVER_BUF_LEN];
struct mkhi_msg *req;
struct mkhi_fwcaps *fwcaps;
struct mei_os_ver *os_ver;
diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c
index df5f78ae3d25..d1928fdd0f43 100644
--- a/drivers/misc/mei/bus.c
+++ b/drivers/misc/mei/bus.c
@@ -1076,12 +1076,6 @@ void mei_cl_bus_rescan_work(struct work_struct *work)
{
struct mei_device *bus =
container_of(work, struct mei_device, bus_rescan_work);
- struct mei_me_client *me_cl;
-
- me_cl = mei_me_cl_by_uuid(bus, &mei_amthif_guid);
- if (me_cl)
- mei_amthif_host_init(bus, me_cl);
- mei_me_cl_put(me_cl);
mei_cl_bus_rescan(bus);
}
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index d3e3372424d6..be64969d986a 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -428,7 +428,7 @@ static inline void mei_io_list_free_cl(struct list_head *head,
* @head: io list
* @fp: file pointer (matching cb file object), may be NULL
*/
-void mei_io_list_free_fp(struct list_head *head, const struct file *fp)
+static void mei_io_list_free_fp(struct list_head *head, const struct file *fp)
{
struct mei_cl_cb *cb, *next;
@@ -554,7 +554,7 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp)
* @cl: host client to be initialized
* @dev: mei device
*/
-void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
+static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev)
{
memset(cl, 0, sizeof(struct mei_cl));
init_waitqueue_head(&cl->wait);
@@ -600,7 +600,6 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev)
int mei_cl_link(struct mei_cl *cl)
{
struct mei_device *dev;
- long open_handle_count;
int id;
if (WARN_ON(!cl || !cl->dev))
@@ -614,8 +613,7 @@ int mei_cl_link(struct mei_cl *cl)
return -EMFILE;
}
- open_handle_count = dev->open_handle_count + dev->iamthif_open_count;
- if (open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) {
+ if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) {
dev_err(dev->dev, "open_handle_count exceeded %d",
MEI_MAX_OPEN_HANDLE_COUNT);
return -EMFILE;
@@ -649,8 +647,7 @@ int mei_cl_unlink(struct mei_cl *cl)
if (!cl)
return 0;
- /* amthif might not be initialized */
- if (!cl->dev)
+ if (WARN_ON(!cl->dev))
return 0;
dev = cl->dev;
@@ -763,7 +760,6 @@ static void mei_cl_set_disconnected(struct mei_cl *cl)
mei_io_list_free_cl(&dev->write_waiting_list, cl);
mei_io_list_flush_cl(&dev->ctrl_rd_list, cl);
mei_io_list_flush_cl(&dev->ctrl_wr_list, cl);
- mei_io_list_free_cl(&dev->amthif_cmd_list, cl);
mei_cl_wake_all(cl);
cl->rx_flow_ctrl_creds = 0;
cl->tx_flow_ctrl_creds = 0;
@@ -1478,7 +1474,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp)
return -ENOTTY;
}
- if (mei_cl_is_fixed_address(cl) || cl == &dev->iamthif_cl)
+ if (mei_cl_is_fixed_address(cl))
return 0;
/* HW currently supports only one pending read */
diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h
index 545ae319ba90..5371df4d8af3 100644
--- a/drivers/misc/mei/client.h
+++ b/drivers/misc/mei/client.h
@@ -83,15 +83,12 @@ static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)
* MEI IO Functions
*/
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
-void mei_io_list_free_fp(struct list_head *head, const struct file *fp);
/*
* MEI Host Client Functions
*/
struct mei_cl *mei_cl_allocate(struct mei_device *dev);
-void mei_cl_init(struct mei_cl *cl, struct mei_device *dev);
-
int mei_cl_link(struct mei_cl *cl);
int mei_cl_unlink(struct mei_cl *cl);
@@ -205,8 +202,6 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp);
-int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_msg_hdr *hdr,
- struct list_head *cmpl_list);
int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb);
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c
index ba3a774c8d71..fe6595fe94f1 100644
--- a/drivers/misc/mei/hbm.c
+++ b/drivers/misc/mei/hbm.c
@@ -166,9 +166,8 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len)
*
* Return: 0 on success, <0 on failure.
*/
-static inline
-int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl,
- u8 hbm_cmd, u8 *buf, size_t len)
+static inline int mei_hbm_cl_write(struct mei_device *dev, struct mei_cl *cl,
+ u8 hbm_cmd, void *buf, size_t len)
{
struct mei_msg_hdr mei_hdr;
@@ -632,11 +631,11 @@ static int mei_hbm_stop_req(struct mei_device *dev)
*/
int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl)
{
- const size_t len = sizeof(struct hbm_flow_control);
- u8 buf[len];
+ struct hbm_flow_control req;
cl_dbg(dev, cl, "sending flow control\n");
- return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD, buf, len);
+ return mei_hbm_cl_write(dev, cl, MEI_FLOW_CONTROL_CMD,
+ &req, sizeof(req));
}
/**
@@ -710,10 +709,10 @@ static void mei_hbm_cl_tx_flow_ctrl_creds_res(struct mei_device *dev,
*/
int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl)
{
- const size_t len = sizeof(struct hbm_client_connect_request);
- u8 buf[len];
+ struct hbm_client_connect_request req;
- return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD, buf, len);
+ return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_REQ_CMD,
+ &req, sizeof(req));
}
/**
@@ -726,10 +725,10 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl)
*/
int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl)
{
- const size_t len = sizeof(struct hbm_client_connect_response);
- u8 buf[len];
+ struct hbm_client_connect_response resp;
- return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD, buf, len);
+ return mei_hbm_cl_write(dev, cl, CLIENT_DISCONNECT_RES_CMD,
+ &resp, sizeof(resp));
}
/**
@@ -763,10 +762,10 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct mei_cl *cl,
*/
int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl)
{
- const size_t len = sizeof(struct hbm_client_connect_request);
- u8 buf[len];
+ struct hbm_client_connect_request req;
- return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD, buf, len);
+ return mei_hbm_cl_write(dev, cl, CLIENT_CONNECT_REQ_CMD,
+ &req, sizeof(req));
}
/**
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c
index 13c55b8f9261..c8ad9ee7cb80 100644
--- a/drivers/misc/mei/init.c
+++ b/drivers/misc/mei/init.c
@@ -146,18 +146,9 @@ int mei_reset(struct mei_device *dev)
/* fall through and remove the sw state even if hw reset has failed */
/* no need to clean up software state in case of power up */
- if (state != MEI_DEV_INITIALIZING &&
- state != MEI_DEV_POWER_UP) {
-
- /* remove all waiting requests */
+ if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_POWER_UP)
mei_cl_all_disconnect(dev);
- /* remove entry if already in list */
- dev_dbg(dev->dev, "remove iamthif from the file list.\n");
- mei_cl_unlink(&dev->iamthif_cl);
- mei_amthif_reset_params(dev);
- }
-
mei_hbm_reset(dev);
dev->rd_msg_hdr = 0;
@@ -401,9 +392,6 @@ void mei_device_init(struct mei_device *dev,
INIT_WORK(&dev->reset_work, mei_reset_work);
INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work);
- INIT_LIST_HEAD(&dev->iamthif_cl.link);
- INIT_LIST_HEAD(&dev->amthif_cmd_list);
-
bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
dev->open_handle_count = 0;
diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c
index 406e9e2b2fff..c14e35201721 100644
--- a/drivers/misc/mei/interrupt.c
+++ b/drivers/misc/mei/interrupt.c
@@ -47,10 +47,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list)
list_del_init(&cb->list);
dev_dbg(dev->dev, "completing call back.\n");
- if (cl == &dev->iamthif_cl)
- mei_amthif_complete(cl, cb);
- else
- mei_cl_complete(cl, cb);
+ mei_cl_complete(cl, cb);
}
}
EXPORT_SYMBOL_GPL(mei_irq_compl_handler);
@@ -76,7 +73,7 @@ static inline int mei_cl_hbm_equal(struct mei_cl *cl,
* @dev: mei device
* @hdr: message header
*/
-void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
+static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
{
/*
* no need to check for size as it is guarantied
@@ -96,9 +93,9 @@ void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr)
*
* Return: always 0
*/
-int mei_cl_irq_read_msg(struct mei_cl *cl,
- struct mei_msg_hdr *mei_hdr,
- struct list_head *cmpl_list)
+static int mei_cl_irq_read_msg(struct mei_cl *cl,
+ struct mei_msg_hdr *mei_hdr,
+ struct list_head *cmpl_list)
{
struct mei_device *dev = cl->dev;
struct mei_cl_cb *cb;
@@ -313,11 +310,7 @@ int mei_irq_read_handler(struct mei_device *dev,
goto end;
}
- if (cl == &dev->iamthif_cl) {
- ret = mei_amthif_irq_read_msg(cl, mei_hdr, cmpl_list);
- } else {
- ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list);
- }
+ ret = mei_cl_irq_read_msg(cl, mei_hdr, cmpl_list);
reset_slots:
@@ -423,10 +416,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
dev_dbg(dev->dev, "complete write list cb.\n");
list_for_each_entry_safe(cb, next, &dev->write_list, list) {
cl = cb->cl;
- if (cl == &dev->iamthif_cl)
- ret = mei_amthif_irq_write(cl, cb, cmpl_list);
- else
- ret = mei_cl_irq_write(cl, cb, cmpl_list);
+ ret = mei_cl_irq_write(cl, cb, cmpl_list);
if (ret)
return ret;
}
@@ -512,20 +502,6 @@ void mei_timer(struct work_struct *work)
}
}
- if (!mei_cl_is_connected(&dev->iamthif_cl))
- goto out;
-
- if (dev->iamthif_stall_timer) {
- if (--dev->iamthif_stall_timer == 0) {
- dev_err(dev->dev, "timer: amthif hanged.\n");
- mei_reset(dev);
-
- mei_amthif_run_next_cmd(dev);
- goto out;
- }
- reschedule_timer = true;
- }
-
out:
if (dev->dev_state != MEI_DEV_DISABLED && reschedule_timer)
mei_schedule_stall_timer(dev);
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index bf816449cd40..e825f013e54e 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -103,10 +103,7 @@ static int mei_release(struct inode *inode, struct file *file)
dev = cl->dev;
mutex_lock(&dev->device_lock);
- if (cl == &dev->iamthif_cl) {
- rets = mei_amthif_release(dev, file);
- goto out;
- }
+
rets = mei_cl_disconnect(cl);
mei_cl_flush_queues(cl, file);
@@ -117,7 +114,7 @@ static int mei_release(struct inode *inode, struct file *file)
file->private_data = NULL;
kfree(cl);
-out:
+
mutex_unlock(&dev->device_lock);
return rets;
}
@@ -182,8 +179,6 @@ static ssize_t mei_read(struct file *file, char __user *ubuf,
goto out;
}
-
-again:
mutex_unlock(&dev->device_lock);
if (wait_event_interruptible(cl->rx_wait,
!list_empty(&cl->rd_completed) ||
@@ -201,14 +196,6 @@ again:
cb = mei_cl_read_cb(cl, file);
if (!cb) {
- /*
- * For amthif all the waiters are woken up,
- * but only fp with matching cb->fp get the cb,
- * the others have to return to wait on read.
- */
- if (cl == &dev->iamthif_cl)
- goto again;
-
rets = 0;
goto out;
}
@@ -319,13 +306,6 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf,
goto out;
}
- if (cl == &dev->iamthif_cl) {
- rets = mei_amthif_write(cl, cb);
- if (!rets)
- rets = length;
- goto out;
- }
-
rets = mei_cl_write(cl, cb);
out:
mutex_unlock(&dev->device_lock);
@@ -388,30 +368,6 @@ static int mei_ioctl_connect_client(struct file *file,
dev_dbg(dev->dev, "FW Client - Max Msg Len = %d\n",
me_cl->props.max_msg_length);
- /* if we're connecting to amthif client then we will use the
- * existing connection
- */
- if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) {
- dev_dbg(dev->dev, "FW Client is amthi\n");
- if (!mei_cl_is_connected(&dev->iamthif_cl)) {
- rets = -ENODEV;
- goto end;
- }
- mei_cl_unlink(cl);
-
- kfree(cl);
- cl = NULL;
- dev->iamthif_open_count++;
- file->private_data = &dev->iamthif_cl;
-
- client = &data->out_client_properties;
- client->max_msg_length = me_cl->props.max_msg_length;
- client->protocol_version = me_cl->props.protocol_version;
- rets = dev->iamthif_cl.status;
-
- goto end;
- }
-
/* prepare the output buffer */
client = &data->out_client_properties;
client->max_msg_length = me_cl->props.max_msg_length;
@@ -615,11 +571,6 @@ static unsigned int mei_poll(struct file *file, poll_table *wait)
mask |= POLLPRI;
}
- if (cl == &dev->iamthif_cl) {
- mask |= mei_amthif_poll(file, wait);
- goto out;
- }
-
if (req_events & (POLLIN | POLLRDNORM)) {
poll_wait(file, &cl->rx_wait, wait);
@@ -635,6 +586,77 @@ out:
}
/**
+ * mei_cl_is_write_queued - check if the client has pending writes.
+ *
+ * @cl: writing host client
+ *
+ * Return: true if client is writing, false otherwise.
+ */
+static bool mei_cl_is_write_queued(struct mei_cl *cl)
+{
+ struct mei_device *dev = cl->dev;
+ struct mei_cl_cb *cb;
+
+ list_for_each_entry(cb, &dev->write_list, list)
+ if (cb->cl == cl)
+ return true;
+ list_for_each_entry(cb, &dev->write_waiting_list, list)
+ if (cb->cl == cl)
+ return true;
+ return false;
+}
+
+/**
+ * mei_fsync - the fsync handler
+ *
+ * @fp: pointer to file structure
+ * @start: unused
+ * @end: unused
+ * @datasync: unused
+ *
+ * Return: 0 on success, -ENODEV if client is not connected
+ */
+static int mei_fsync(struct file *fp, loff_t start, loff_t end, int datasync)
+{
+ struct mei_cl *cl = fp->private_data;
+ struct mei_device *dev;
+ int rets;
+
+ if (WARN_ON(!cl || !cl->dev))
+ return -ENODEV;
+
+ dev = cl->dev;
+
+ mutex_lock(&dev->device_lock);
+
+ if (dev->dev_state != MEI_DEV_ENABLED || !mei_cl_is_connected(cl)) {
+ rets = -ENODEV;
+ goto out;
+ }
+
+ while (mei_cl_is_write_queued(cl)) {
+ mutex_unlock(&dev->device_lock);
+ rets = wait_event_interruptible(cl->tx_wait,
+ cl->writing_state == MEI_WRITE_COMPLETE ||
+ !mei_cl_is_connected(cl));
+ mutex_lock(&dev->device_lock);
+ if (rets) {
+ if (signal_pending(current))
+ rets = -EINTR;
+ goto out;
+ }
+ if (!mei_cl_is_connected(cl)) {
+ rets = -ENODEV;
+ goto out;
+ }
+ }
+ rets = 0;
+out:
+ mutex_unlock(&dev->device_lock);
+ return rets;
+}
+
+/**
* mei_fasync - asynchronous io support
*
* @fd: file descriptor
@@ -749,6 +771,7 @@ static const struct file_operations mei_fops = {
.release = mei_release,
.write = mei_write,
.poll = mei_poll,
+ .fsync = mei_fsync,
.fasync = mei_fasync,
.llseek = no_llseek
};
diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h
index d41aac53a2ac..63a67c99fc78 100644
--- a/drivers/misc/mei/mei_dev.h
+++ b/drivers/misc/mei/mei_dev.h
@@ -26,12 +26,6 @@
#include "hw.h"
#include "hbm.h"
-
-/*
- * AMTHI Client UUID
- */
-extern const uuid_le mei_amthif_guid;
-
#define MEI_RD_MSG_BUF_SIZE (128 * sizeof(u32))
/*
@@ -78,12 +72,6 @@ enum mei_dev_state {
const char *mei_dev_state_str(int state);
-enum iamthif_states {
- MEI_IAMTHIF_IDLE,
- MEI_IAMTHIF_WRITING,
- MEI_IAMTHIF_READING,
-};
-
enum mei_file_transaction_states {
MEI_IDLE,
MEI_WRITING,
@@ -418,13 +406,6 @@ const char *mei_pg_state_str(enum mei_pg_state state);
* @allow_fixed_address: allow user space to connect a fixed client
* @override_fixed_address: force allow fixed address behavior
*
- * @amthif_cmd_list : amthif list for cmd waiting
- * @iamthif_cl : amthif host client
- * @iamthif_open_count : number of opened amthif connections
- * @iamthif_stall_timer : timer to detect amthif hang
- * @iamthif_state : amthif processor state
- * @iamthif_canceled : current amthif command is canceled
- *
* @reset_work : work item for the device reset
* @bus_rescan_work : work item for the bus rescan
*
@@ -500,14 +481,6 @@ struct mei_device {
bool allow_fixed_address;
bool override_fixed_address;
- /* amthif list for cmd waiting */
- struct list_head amthif_cmd_list;
- struct mei_cl iamthif_cl;
- long iamthif_open_count;
- u32 iamthif_stall_timer;
- enum iamthif_states iamthif_state;
- bool iamthif_canceled;
-
struct work_struct reset_work;
struct work_struct bus_rescan_work;
@@ -579,28 +552,6 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list);
void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list);
/*
- * AMTHIF - AMT Host Interface Functions
- */
-void mei_amthif_reset_params(struct mei_device *dev);
-
-int mei_amthif_host_init(struct mei_device *dev, struct mei_me_client *me_cl);
-
-unsigned int mei_amthif_poll(struct file *file, poll_table *wait);
-
-int mei_amthif_release(struct mei_device *dev, struct file *file);
-
-int mei_amthif_write(struct mei_cl *cl, struct mei_cl_cb *cb);
-int mei_amthif_run_next_cmd(struct mei_device *dev);
-int mei_amthif_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
- struct list_head *cmpl_list);
-
-void mei_amthif_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
-int mei_amthif_irq_read_msg(struct mei_cl *cl,
- struct mei_msg_hdr *mei_hdr,
- struct list_head *cmpl_list);
-int mei_amthif_irq_read(struct mei_device *dev, s32 *slots);
-
-/*
* Register Access Function
*/
@@ -711,8 +662,6 @@ bool mei_hbuf_acquire(struct mei_device *dev);
bool mei_write_is_idle(struct mei_device *dev);
-void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr);
-
#if IS_ENABLED(CONFIG_DEBUG_FS)
int mei_dbgfs_register(struct mei_device *dev, const char *name);
void mei_dbgfs_deregister(struct mei_device *dev);
diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
index 0a668fdfbbe9..8621a198a2ce 100644
--- a/drivers/misc/mei/pci-me.c
+++ b/drivers/misc/mei/pci-me.c
@@ -242,11 +242,38 @@ end:
}
/**
+ * mei_me_shutdown - Device Removal Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * mei_me_shutdown is called from the reboot notifier
+ * it's a simplified version of remove so we go down
+ * faster.
+ */
+static void mei_me_shutdown(struct pci_dev *pdev)
+{
+ struct mei_device *dev;
+
+ dev = pci_get_drvdata(pdev);
+ if (!dev)
+ return;
+
+ dev_dbg(&pdev->dev, "shutdown\n");
+ mei_stop(dev);
+
+ if (!pci_dev_run_wake(pdev))
+ mei_me_unset_pm_domain(dev);
+
+ mei_disable_interrupts(dev);
+ free_irq(pdev->irq, dev);
+}
+
+/**
* mei_me_remove - Device Removal Routine
*
* @pdev: PCI device structure
*
- * mei_remove is called by the PCI subsystem to alert the driver
+ * mei_me_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device.
*/
static void mei_me_remove(struct pci_dev *pdev)
@@ -456,7 +483,7 @@ static struct pci_driver mei_me_driver = {
.id_table = mei_me_pci_tbl,
.probe = mei_me_probe,
.remove = mei_me_remove,
- .shutdown = mei_me_remove,
+ .shutdown = mei_me_shutdown,
.driver.pm = MEI_ME_PM_OPS,
};
diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c
index fe088b40daf9..f811cd524468 100644
--- a/drivers/misc/mei/pci-txe.c
+++ b/drivers/misc/mei/pci-txe.c
@@ -161,6 +161,33 @@ end:
}
/**
+ * mei_txe_remove - Device Shutdown Routine
+ *
+ * @pdev: PCI device structure
+ *
+ * mei_txe_shutdown is called from the reboot notifier
+ * it's a simplified version of remove so we go down
+ * faster.
+ */
+static void mei_txe_shutdown(struct pci_dev *pdev)
+{
+ struct mei_device *dev;
+
+ dev = pci_get_drvdata(pdev);
+ if (!dev)
+ return;
+
+ dev_dbg(&pdev->dev, "shutdown\n");
+ mei_stop(dev);
+
+ if (!pci_dev_run_wake(pdev))
+ mei_txe_unset_pm_domain(dev);
+
+ mei_disable_interrupts(dev);
+ free_irq(pdev->irq, dev);
+}
+
+/**
* mei_txe_remove - Device Removal Routine
*
* @pdev: PCI device structure
@@ -386,7 +413,7 @@ static struct pci_driver mei_txe_driver = {
.id_table = mei_txe_pci_tbl,
.probe = mei_txe_probe,
.remove = mei_txe_remove,
- .shutdown = mei_txe_remove,
+ .shutdown = mei_txe_shutdown,
.driver.pm = MEI_TXE_PM_OPS,
};
diff --git a/drivers/misc/tsl2550.c b/drivers/misc/tsl2550.c
index 87a13374fdc0..adf46072cb37 100644
--- a/drivers/misc/tsl2550.c
+++ b/drivers/misc/tsl2550.c
@@ -443,9 +443,16 @@ static const struct i2c_device_id tsl2550_id[] = {
};
MODULE_DEVICE_TABLE(i2c, tsl2550_id);
+static const struct of_device_id tsl2550_of_match[] = {
+ { .compatible = "taos,tsl2550" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tsl2550_of_match);
+
static struct i2c_driver tsl2550_driver = {
.driver = {
.name = TSL2550_DRV_NAME,
+ .of_match_table = tsl2550_of_match,
.pm = TSL2550_PM_OPS,
},
.probe = tsl2550_probe,
diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 77513195f50e..8bae3731d039 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -421,41 +421,6 @@ static void dev_release(struct device *dev)
}
/**
- * ubi_sysfs_init - initialize sysfs for an UBI device.
- * @ubi: UBI device description object
- * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was
- * taken
- *
- * This function returns zero in case of success and a negative error code in
- * case of failure.
- */
-static int ubi_sysfs_init(struct ubi_device *ubi, int *ref)
-{
- int err;
-
- ubi->dev.release = dev_release;
- ubi->dev.devt = ubi->cdev.dev;
- ubi->dev.class = &ubi_class;
- ubi->dev.groups = ubi_dev_groups;
- dev_set_name(&ubi->dev, UBI_NAME_STR"%d", ubi->ubi_num);
- err = device_register(&ubi->dev);
- if (err)
- return err;
-
- *ref = 1;
- return 0;
-}
-
-/**
- * ubi_sysfs_close - close sysfs for an UBI device.
- * @ubi: UBI device description object
- */
-static void ubi_sysfs_close(struct ubi_device *ubi)
-{
- device_unregister(&ubi->dev);
-}
-
-/**
* kill_volumes - destroy all user volumes.
* @ubi: UBI device description object
*/
@@ -471,27 +436,19 @@ static void kill_volumes(struct ubi_device *ubi)
/**
* uif_init - initialize user interfaces for an UBI device.
* @ubi: UBI device description object
- * @ref: set to %1 on exit in case of failure if a reference to @ubi->dev was
- * taken, otherwise set to %0
*
* This function initializes various user interfaces for an UBI device. If the
* initialization fails at an early stage, this function frees all the
- * resources it allocated, returns an error, and @ref is set to %0. However,
- * if the initialization fails after the UBI device was registered in the
- * driver core subsystem, this function takes a reference to @ubi->dev, because
- * otherwise the release function ('dev_release()') would free whole @ubi
- * object. The @ref argument is set to %1 in this case. The caller has to put
- * this reference.
+ * resources it allocated, returns an error.
*
* This function returns zero in case of success and a negative error code in
* case of failure.
*/
-static int uif_init(struct ubi_device *ubi, int *ref)
+static int uif_init(struct ubi_device *ubi)
{
int i, err;
dev_t dev;
- *ref = 0;
sprintf(ubi->ubi_name, UBI_NAME_STR "%d", ubi->ubi_num);
/*
@@ -508,20 +465,17 @@ static int uif_init(struct ubi_device *ubi, int *ref)
return err;
}
+ ubi->dev.devt = dev;
+
ubi_assert(MINOR(dev) == 0);
cdev_init(&ubi->cdev, &ubi_cdev_operations);
dbg_gen("%s major is %u", ubi->ubi_name, MAJOR(dev));
ubi->cdev.owner = THIS_MODULE;
- err = cdev_add(&ubi->cdev, dev, 1);
- if (err) {
- ubi_err(ubi, "cannot add character device");
- goto out_unreg;
- }
-
- err = ubi_sysfs_init(ubi, ref);
+ dev_set_name(&ubi->dev, UBI_NAME_STR "%d", ubi->ubi_num);
+ err = cdev_device_add(&ubi->cdev, &ubi->dev);
if (err)
- goto out_sysfs;
+ goto out_unreg;
for (i = 0; i < ubi->vtbl_slots; i++)
if (ubi->volumes[i]) {
@@ -536,11 +490,7 @@ static int uif_init(struct ubi_device *ubi, int *ref)
out_volumes:
kill_volumes(ubi);
-out_sysfs:
- if (*ref)
- get_device(&ubi->dev);
- ubi_sysfs_close(ubi);
- cdev_del(&ubi->cdev);
+ cdev_device_del(&ubi->cdev, &ubi->dev);
out_unreg:
unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1);
ubi_err(ubi, "cannot initialize UBI %s, error %d",
@@ -559,8 +509,7 @@ out_unreg:
static void uif_close(struct ubi_device *ubi)
{
kill_volumes(ubi);
- ubi_sysfs_close(ubi);
- cdev_del(&ubi->cdev);
+ cdev_device_del(&ubi->cdev, &ubi->dev);
unregister_chrdev_region(ubi->cdev.dev, ubi->vtbl_slots + 1);
}
@@ -857,7 +806,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
int vid_hdr_offset, int max_beb_per1024)
{
struct ubi_device *ubi;
- int i, err, ref = 0;
+ int i, err;
if (max_beb_per1024 < 0 || max_beb_per1024 > MAX_MTD_UBI_BEB_LIMIT)
return -EINVAL;
@@ -919,6 +868,11 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
if (!ubi)
return -ENOMEM;
+ device_initialize(&ubi->dev);
+ ubi->dev.release = dev_release;
+ ubi->dev.class = &ubi_class;
+ ubi->dev.groups = ubi_dev_groups;
+
ubi->mtd = mtd;
ubi->ubi_num = ubi_num;
ubi->vid_hdr_offset = vid_hdr_offset;
@@ -995,7 +949,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
/* Make device "available" before it becomes accessible via sysfs */
ubi_devices[ubi_num] = ubi;
- err = uif_init(ubi, &ref);
+ err = uif_init(ubi);
if (err)
goto out_detach;
@@ -1045,8 +999,6 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
out_debugfs:
ubi_debugfs_exit_dev(ubi);
out_uif:
- get_device(&ubi->dev);
- ubi_assert(ref);
uif_close(ubi);
out_detach:
ubi_devices[ubi_num] = NULL;
@@ -1056,10 +1008,7 @@ out_detach:
out_free:
vfree(ubi->peb_buf);
vfree(ubi->fm_buf);
- if (ref)
- put_device(&ubi->dev);
- else
- kfree(ubi);
+ put_device(&ubi->dev);
return err;
}
@@ -1120,12 +1069,6 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway)
if (ubi->bgt_thread)
kthread_stop(ubi->bgt_thread);
- /*
- * Get a reference to the device in order to prevent 'dev_release()'
- * from freeing the @ubi object.
- */
- get_device(&ubi->dev);
-
ubi_debugfs_exit_dev(ubi);
uif_close(ubi);
diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c
index 7ac78c13dd1c..85237cf661f9 100644
--- a/drivers/mtd/ubi/vmt.c
+++ b/drivers/mtd/ubi/vmt.c
@@ -155,11 +155,10 @@ static void vol_release(struct device *dev)
*/
int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
{
- int i, err, vol_id = req->vol_id, do_free = 1;
+ int i, err, vol_id = req->vol_id;
struct ubi_volume *vol;
struct ubi_vtbl_record vtbl_rec;
struct ubi_eba_table *eba_tbl = NULL;
- dev_t dev;
if (ubi->ro_mode)
return -EROFS;
@@ -168,6 +167,12 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
if (!vol)
return -ENOMEM;
+ device_initialize(&vol->dev);
+ vol->dev.release = vol_release;
+ vol->dev.parent = &ubi->dev;
+ vol->dev.class = &ubi_class;
+ vol->dev.groups = volume_dev_groups;
+
spin_lock(&ubi->volumes_lock);
if (vol_id == UBI_VOL_NUM_AUTO) {
/* Find unused volume ID */
@@ -268,24 +273,13 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
/* Register character device for the volume */
cdev_init(&vol->cdev, &ubi_vol_cdev_operations);
vol->cdev.owner = THIS_MODULE;
- dev = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1);
- err = cdev_add(&vol->cdev, dev, 1);
- if (err) {
- ubi_err(ubi, "cannot add character device");
- goto out_mapping;
- }
-
- vol->dev.release = vol_release;
- vol->dev.parent = &ubi->dev;
- vol->dev.devt = dev;
- vol->dev.class = &ubi_class;
- vol->dev.groups = volume_dev_groups;
+ vol->dev.devt = MKDEV(MAJOR(ubi->cdev.dev), vol_id + 1);
dev_set_name(&vol->dev, "%s_%d", ubi->ubi_name, vol->vol_id);
- err = device_register(&vol->dev);
+ err = cdev_device_add(&vol->cdev, &vol->dev);
if (err) {
- ubi_err(ubi, "cannot register device");
- goto out_cdev;
+ ubi_err(ubi, "cannot add device");
+ goto out_mapping;
}
/* Fill volume table record */
@@ -318,28 +312,17 @@ out_sysfs:
* We have registered our device, we should not free the volume
* description object in this function in case of an error - it is
* freed by the release function.
- *
- * Get device reference to prevent the release function from being
- * called just after sysfs has been closed.
*/
- do_free = 0;
- get_device(&vol->dev);
- device_unregister(&vol->dev);
-out_cdev:
- cdev_del(&vol->cdev);
+ cdev_device_del(&vol->cdev, &vol->dev);
out_mapping:
- if (do_free)
- ubi_eba_destroy_table(eba_tbl);
+ ubi_eba_destroy_table(eba_tbl);
out_acc:
spin_lock(&ubi->volumes_lock);
ubi->rsvd_pebs -= vol->reserved_pebs;
ubi->avail_pebs += vol->reserved_pebs;
out_unlock:
spin_unlock(&ubi->volumes_lock);
- if (do_free)
- kfree(vol);
- else
- put_device(&vol->dev);
+ put_device(&vol->dev);
ubi_err(ubi, "cannot create volume %d, error %d", vol_id, err);
return err;
}
@@ -391,8 +374,8 @@ int ubi_remove_volume(struct ubi_volume_desc *desc, int no_vtbl)
goto out_err;
}
- cdev_del(&vol->cdev);
- device_unregister(&vol->dev);
+ cdev_device_del(&vol->cdev, &vol->dev);
+ put_device(&vol->dev);
spin_lock(&ubi->volumes_lock);
ubi->rsvd_pebs -= reserved_pebs;
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 650f1b1797ad..101ced4c84be 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -13,6 +13,17 @@ menuconfig NVMEM
if NVMEM
+config NVMEM_IMX_IIM
+ tristate "i.MX IC Identification Module support"
+ depends on ARCH_MXC || COMPILE_TEST
+ help
+ This is a driver for the IC Identification Module (IIM) available on
+ i.MX SoCs, providing access to 4 Kbits of programmable
+ eFuses.
+
+ This driver can also be built as a module. If so, the module
+ will be called nvmem-imx-iim.
+
config NVMEM_IMX_OCOTP
tristate "i.MX6 On-Chip OTP Controller support"
depends on SOC_IMX6 || COMPILE_TEST
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 86e45995fdad..173140658693 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -8,6 +8,8 @@ nvmem_core-y := core.o
# Devices
obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o
nvmem-bcm-ocotp-y := bcm-ocotp.o
+obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-imx-iim.o
+nvmem-imx-iim-y := imx-iim.o
obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o
nvmem-imx-ocotp-y := imx-ocotp.o
obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 408b521ee520..8c830a80a648 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -468,7 +468,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
np = config->dev->of_node;
nvmem->dev.of_node = np;
dev_set_name(&nvmem->dev, "%s%d",
- config->name ? : "nvmem", config->id);
+ config->name ? : "nvmem",
+ config->name ? config->id : nvmem->id);
nvmem->read_only = of_property_read_bool(np, "read-only") |
config->read_only;
diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c
new file mode 100644
index 000000000000..52ff65e0673f
--- /dev/null
+++ b/drivers/nvmem/imx-iim.c
@@ -0,0 +1,173 @@
+/*
+ * i.MX IIM driver
+ *
+ * Copyright (c) 2017 Pengutronix, Michael Grzeschik <m.grzeschik@pengutronix.de>
+ *
+ * Based on the barebox iim driver,
+ * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>,
+ * Orex Computed Radiography
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#define IIM_BANK_BASE(n) (0x800 + 0x400 * (n))
+
+struct imx_iim_drvdata {
+ unsigned int nregs;
+};
+
+struct iim_priv {
+ void __iomem *base;
+ struct clk *clk;
+ struct nvmem_config nvmem;
+};
+
+static int imx_iim_read(void *context, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ struct iim_priv *iim = context;
+ int i, ret;
+ u8 *buf8 = buf;
+
+ ret = clk_prepare_enable(iim->clk);
+ if (ret)
+ return ret;
+
+ for (i = offset; i < offset + bytes; i++) {
+ int bank = i >> 5;
+ int reg = i & 0x1f;
+
+ *buf8++ = readl(iim->base + IIM_BANK_BASE(bank) + reg * 4);
+ }
+
+ clk_disable_unprepare(iim->clk);
+
+ return 0;
+}
+
+static struct imx_iim_drvdata imx27_drvdata = {
+ .nregs = 2 * 32,
+};
+
+static struct imx_iim_drvdata imx25_imx31_imx35_drvdata = {
+ .nregs = 3 * 32,
+};
+
+static struct imx_iim_drvdata imx51_drvdata = {
+ .nregs = 4 * 32,
+};
+
+static struct imx_iim_drvdata imx53_drvdata = {
+ .nregs = 4 * 32 + 16,
+};
+
+static const struct of_device_id imx_iim_dt_ids[] = {
+ {
+ .compatible = "fsl,imx25-iim",
+ .data = &imx25_imx31_imx35_drvdata,
+ }, {
+ .compatible = "fsl,imx27-iim",
+ .data = &imx27_drvdata,
+ }, {
+ .compatible = "fsl,imx31-iim",
+ .data = &imx25_imx31_imx35_drvdata,
+ }, {
+ .compatible = "fsl,imx35-iim",
+ .data = &imx25_imx31_imx35_drvdata,
+ }, {
+ .compatible = "fsl,imx51-iim",
+ .data = &imx51_drvdata,
+ }, {
+ .compatible = "fsl,imx53-iim",
+ .data = &imx53_drvdata,
+ }, {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(of, imx_iim_dt_ids);
+
+static int imx_iim_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct iim_priv *iim;
+ struct nvmem_device *nvmem;
+ struct nvmem_config *cfg;
+ const struct imx_iim_drvdata *drvdata = NULL;
+
+ iim = devm_kzalloc(dev, sizeof(*iim), GFP_KERNEL);
+ if (!iim)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ iim->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(iim->base))
+ return PTR_ERR(iim->base);
+
+ of_id = of_match_device(imx_iim_dt_ids, dev);
+ if (!of_id)
+ return -ENODEV;
+
+ drvdata = of_id->data;
+
+ iim->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(iim->clk))
+ return PTR_ERR(iim->clk);
+
+ cfg = &iim->nvmem;
+
+ cfg->name = "imx-iim",
+ cfg->read_only = true,
+ cfg->word_size = 1,
+ cfg->stride = 1,
+ cfg->owner = THIS_MODULE,
+ cfg->reg_read = imx_iim_read,
+ cfg->dev = dev;
+ cfg->size = drvdata->nregs;
+ cfg->priv = iim;
+
+ nvmem = nvmem_register(cfg);
+ if (IS_ERR(nvmem))
+ return PTR_ERR(nvmem);
+
+ platform_set_drvdata(pdev, nvmem);
+
+ return 0;
+}
+
+static int imx_iim_remove(struct platform_device *pdev)
+{
+ struct nvmem_device *nvmem = platform_get_drvdata(pdev);
+
+ return nvmem_unregister(nvmem);
+}
+
+static struct platform_driver imx_iim_driver = {
+ .probe = imx_iim_probe,
+ .remove = imx_iim_remove,
+ .driver = {
+ .name = "imx-iim",
+ .of_match_table = imx_iim_dt_ids,
+ },
+};
+module_platform_driver(imx_iim_driver);
+
+MODULE_AUTHOR("Michael Grzeschik <m.grzeschik@pengutronix.de>");
+MODULE_DESCRIPTION("i.MX IIM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c
index b8ca1e677b01..193ca8fd350a 100644
--- a/drivers/nvmem/imx-ocotp.c
+++ b/drivers/nvmem/imx-ocotp.c
@@ -7,6 +7,9 @@
* Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>,
* Orex Computed Radiography
*
+ * Write support based on the fsl_otp driver,
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
@@ -24,14 +27,88 @@
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
+#include <linux/delay.h>
+
+#define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the
+ * OTP Bank0 Word0
+ */
+#define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr
+ * of two consecutive OTP words.
+ */
+
+#define IMX_OCOTP_ADDR_CTRL 0x0000
+#define IMX_OCOTP_ADDR_CTRL_SET 0x0004
+#define IMX_OCOTP_ADDR_CTRL_CLR 0x0008
+#define IMX_OCOTP_ADDR_TIMING 0x0010
+#define IMX_OCOTP_ADDR_DATA 0x0020
+
+#define IMX_OCOTP_BM_CTRL_ADDR 0x0000007F
+#define IMX_OCOTP_BM_CTRL_BUSY 0x00000100
+#define IMX_OCOTP_BM_CTRL_ERROR 0x00000200
+#define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400
+
+#define DEF_RELAX 20 /* > 16.5ns */
+#define IMX_OCOTP_WR_UNLOCK 0x3E770000
+#define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA
+
+static DEFINE_MUTEX(ocotp_mutex);
struct ocotp_priv {
struct device *dev;
struct clk *clk;
void __iomem *base;
unsigned int nregs;
+ struct nvmem_config *config;
};
+static int imx_ocotp_wait_for_busy(void __iomem *base, u32 flags)
+{
+ int count;
+ u32 c, mask;
+
+ mask = IMX_OCOTP_BM_CTRL_BUSY | IMX_OCOTP_BM_CTRL_ERROR | flags;
+
+ for (count = 10000; count >= 0; count--) {
+ c = readl(base + IMX_OCOTP_ADDR_CTRL);
+ if (!(c & mask))
+ break;
+ cpu_relax();
+ }
+
+ if (count < 0) {
+ /* HW_OCOTP_CTRL[ERROR] will be set under the following
+ * conditions:
+ * - A write is performed to a shadow register during a shadow
+ * reload (essentially, while HW_OCOTP_CTRL[RELOAD_SHADOWS] is
+ * set. In addition, the contents of the shadow register shall
+ * not be updated.
+ * - A write is performed to a shadow register which has been
+ * locked.
+ * - A read is performed to from a shadow register which has
+ * been read locked.
+ * - A program is performed to a fuse word which has been locked
+ * - A read is performed to from a fuse word which has been read
+ * locked.
+ */
+ if (c & IMX_OCOTP_BM_CTRL_ERROR)
+ return -EPERM;
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static void imx_ocotp_clr_err_if_set(void __iomem *base)
+{
+ u32 c;
+
+ c = readl(base + IMX_OCOTP_ADDR_CTRL);
+ if (!(c & IMX_OCOTP_BM_CTRL_ERROR))
+ return;
+
+ writel(IMX_OCOTP_BM_CTRL_ERROR, base + IMX_OCOTP_ADDR_CTRL_CLR);
+}
+
static int imx_ocotp_read(void *context, unsigned int offset,
void *val, size_t bytes)
{
@@ -47,26 +124,188 @@ static int imx_ocotp_read(void *context, unsigned int offset,
if (count > (priv->nregs - index))
count = priv->nregs - index;
+ mutex_lock(&ocotp_mutex);
+
ret = clk_prepare_enable(priv->clk);
if (ret < 0) {
+ mutex_unlock(&ocotp_mutex);
dev_err(priv->dev, "failed to prepare/enable ocotp clk\n");
return ret;
}
- for (i = index; i < (index + count); i++)
- *buf++ = readl(priv->base + 0x400 + i * 0x10);
+ ret = imx_ocotp_wait_for_busy(priv->base, 0);
+ if (ret < 0) {
+ dev_err(priv->dev, "timeout during read setup\n");
+ goto read_end;
+ }
+
+ for (i = index; i < (index + count); i++) {
+ *buf++ = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 +
+ i * IMX_OCOTP_OFFSET_PER_WORD);
+
+ /* 47.3.1.2
+ * For "read locked" registers 0xBADABADA will be returned and
+ * HW_OCOTP_CTRL[ERROR] will be set. It must be cleared by
+ * software before any new write, read or reload access can be
+ * issued
+ */
+ if (*(buf - 1) == IMX_OCOTP_READ_LOCKED_VAL)
+ imx_ocotp_clr_err_if_set(priv->base);
+ }
+ ret = 0;
+
+read_end:
clk_disable_unprepare(priv->clk);
+ mutex_unlock(&ocotp_mutex);
+ return ret;
+}
- return 0;
+static int imx_ocotp_write(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct ocotp_priv *priv = context;
+ u32 *buf = val;
+ int ret;
+
+ unsigned long clk_rate = 0;
+ unsigned long strobe_read, relax, strobe_prog;
+ u32 timing = 0;
+ u32 ctrl;
+ u8 waddr;
+
+ /* allow only writing one complete OTP word at a time */
+ if ((bytes != priv->config->word_size) ||
+ (offset % priv->config->word_size))
+ return -EINVAL;
+
+ mutex_lock(&ocotp_mutex);
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret < 0) {
+ mutex_unlock(&ocotp_mutex);
+ dev_err(priv->dev, "failed to prepare/enable ocotp clk\n");
+ return ret;
+ }
+
+ /* 47.3.1.3.1
+ * Program HW_OCOTP_TIMING[STROBE_PROG] and HW_OCOTP_TIMING[RELAX]
+ * fields with timing values to match the current frequency of the
+ * ipg_clk. OTP writes will work at maximum bus frequencies as long
+ * as the HW_OCOTP_TIMING parameters are set correctly.
+ */
+ clk_rate = clk_get_rate(priv->clk);
+
+ relax = clk_rate / (1000000000 / DEF_RELAX) - 1;
+ strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1;
+ strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1;
+
+ timing = strobe_prog & 0x00000FFF;
+ timing |= (relax << 12) & 0x0000F000;
+ timing |= (strobe_read << 16) & 0x003F0000;
+
+ writel(timing, priv->base + IMX_OCOTP_ADDR_TIMING);
+
+ /* 47.3.1.3.2
+ * Check that HW_OCOTP_CTRL[BUSY] and HW_OCOTP_CTRL[ERROR] are clear.
+ * Overlapped accesses are not supported by the controller. Any pending
+ * write or reload must be completed before a write access can be
+ * requested.
+ */
+ ret = imx_ocotp_wait_for_busy(priv->base, 0);
+ if (ret < 0) {
+ dev_err(priv->dev, "timeout during timing setup\n");
+ goto write_end;
+ }
+
+ /* 47.3.1.3.3
+ * Write the requested address to HW_OCOTP_CTRL[ADDR] and program the
+ * unlock code into HW_OCOTP_CTRL[WR_UNLOCK]. This must be programmed
+ * for each write access. The lock code is documented in the register
+ * description. Both the unlock code and address can be written in the
+ * same operation.
+ */
+ /* OTP write/read address specifies one of 128 word address locations */
+ waddr = offset / 4;
+
+ ctrl = readl(priv->base + IMX_OCOTP_ADDR_CTRL);
+ ctrl &= ~IMX_OCOTP_BM_CTRL_ADDR;
+ ctrl |= waddr & IMX_OCOTP_BM_CTRL_ADDR;
+ ctrl |= IMX_OCOTP_WR_UNLOCK;
+
+ writel(ctrl, priv->base + IMX_OCOTP_ADDR_CTRL);
+
+ /* 47.3.1.3.4
+ * Write the data to the HW_OCOTP_DATA register. This will automatically
+ * set HW_OCOTP_CTRL[BUSY] and clear HW_OCOTP_CTRL[WR_UNLOCK]. To
+ * protect programming same OTP bit twice, before program OCOTP will
+ * automatically read fuse value in OTP and use read value to mask
+ * program data. The controller will use masked program data to program
+ * a 32-bit word in the OTP per the address in HW_OCOTP_CTRL[ADDR]. Bit
+ * fields with 1's will result in that OTP bit being programmed. Bit
+ * fields with 0's will be ignored. At the same time that the write is
+ * accepted, the controller makes an internal copy of
+ * HW_OCOTP_CTRL[ADDR] which cannot be updated until the next write
+ * sequence is initiated. This copy guarantees that erroneous writes to
+ * HW_OCOTP_CTRL[ADDR] will not affect an active write operation. It
+ * should also be noted that during the programming HW_OCOTP_DATA will
+ * shift right (with zero fill). This shifting is required to program
+ * the OTP serially. During the write operation, HW_OCOTP_DATA cannot be
+ * modified.
+ */
+ writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA);
+
+ /* 47.4.1.4.5
+ * Once complete, the controller will clear BUSY. A write request to a
+ * protected or locked region will result in no OTP access and no
+ * setting of HW_OCOTP_CTRL[BUSY]. In addition HW_OCOTP_CTRL[ERROR] will
+ * be set. It must be cleared by software before any new write access
+ * can be issued.
+ */
+ ret = imx_ocotp_wait_for_busy(priv->base, 0);
+ if (ret < 0) {
+ if (ret == -EPERM) {
+ dev_err(priv->dev, "failed write to locked region");
+ imx_ocotp_clr_err_if_set(priv->base);
+ } else {
+ dev_err(priv->dev, "timeout during data write\n");
+ }
+ goto write_end;
+ }
+
+ /* 47.3.1.4
+ * Write Postamble: Due to internal electrical characteristics of the
+ * OTP during writes, all OTP operations following a write must be
+ * separated by 2 us after the clearing of HW_OCOTP_CTRL_BUSY following
+ * the write.
+ */
+ udelay(2);
+
+ /* reload all shadow registers */
+ writel(IMX_OCOTP_BM_CTRL_REL_SHADOWS,
+ priv->base + IMX_OCOTP_ADDR_CTRL_SET);
+ ret = imx_ocotp_wait_for_busy(priv->base,
+ IMX_OCOTP_BM_CTRL_REL_SHADOWS);
+ if (ret < 0) {
+ dev_err(priv->dev, "timeout during shadow register reload\n");
+ goto write_end;
+ }
+
+write_end:
+ clk_disable_unprepare(priv->clk);
+ mutex_unlock(&ocotp_mutex);
+ if (ret < 0)
+ return ret;
+ return bytes;
}
static struct nvmem_config imx_ocotp_nvmem_config = {
.name = "imx-ocotp",
- .read_only = true,
+ .read_only = false,
.word_size = 4,
.stride = 4,
.owner = THIS_MODULE,
.reg_read = imx_ocotp_read,
+ .reg_write = imx_ocotp_write,
};
static const struct of_device_id imx_ocotp_dt_ids[] = {
@@ -74,6 +313,7 @@ static const struct of_device_id imx_ocotp_dt_ids[] = {
{ .compatible = "fsl,imx6sl-ocotp", (void *)64 },
{ .compatible = "fsl,imx6sx-ocotp", (void *)128 },
{ .compatible = "fsl,imx6ul-ocotp", (void *)128 },
+ { .compatible = "fsl,imx7d-ocotp", (void *)64 },
{ },
};
MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
@@ -90,12 +330,14 @@ static int imx_ocotp_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
+ priv->dev = dev;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
- priv->clk = devm_clk_get(&pdev->dev, NULL);
+ priv->clk = devm_clk_get(dev, NULL);
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
@@ -104,7 +346,9 @@ static int imx_ocotp_probe(struct platform_device *pdev)
imx_ocotp_nvmem_config.size = 4 * priv->nregs;
imx_ocotp_nvmem_config.dev = dev;
imx_ocotp_nvmem_config.priv = priv;
+ priv->config = &imx_ocotp_nvmem_config;
nvmem = nvmem_register(&imx_ocotp_nvmem_config);
+
if (IS_ERR(nvmem))
return PTR_ERR(nvmem);
diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c
index 1567ccca8de3..0d6648be93b8 100644
--- a/drivers/nvmem/sunxi_sid.c
+++ b/drivers/nvmem/sunxi_sid.c
@@ -17,13 +17,24 @@
#include <linux/device.h>
#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/random.h>
+/* Registers and special values for doing register-based SID readout on H3 */
+#define SUN8I_SID_PRCTL 0x40
+#define SUN8I_SID_RDKEY 0x60
+
+#define SUN8I_SID_OFFSET_MASK 0x1FF
+#define SUN8I_SID_OFFSET_SHIFT 16
+#define SUN8I_SID_OP_LOCK (0xAC << 8)
+#define SUN8I_SID_READ BIT(1)
+
static struct nvmem_config econfig = {
.name = "sunxi-sid",
.read_only = true,
@@ -32,8 +43,15 @@ static struct nvmem_config econfig = {
.owner = THIS_MODULE,
};
+struct sunxi_sid_cfg {
+ u32 value_offset;
+ u32 size;
+ bool need_register_readout;
+};
+
struct sunxi_sid {
void __iomem *base;
+ u32 value_offset;
};
/* We read the entire key, due to a 32 bit read alignment requirement. Since we
@@ -58,12 +76,36 @@ static int sunxi_sid_read(void *context, unsigned int offset,
struct sunxi_sid *sid = context;
u8 *buf = val;
+ /* Offset the read operation to the real position of SID */
+ offset += sid->value_offset;
+
while (bytes--)
*buf++ = sunxi_sid_read_byte(sid, offset++);
return 0;
}
+static int sun8i_sid_register_readout(const struct sunxi_sid *sid,
+ const unsigned int word)
+{
+ u32 reg_val;
+ int ret;
+
+ /* Set word, lock access, and set read command */
+ reg_val = (word & SUN8I_SID_OFFSET_MASK)
+ << SUN8I_SID_OFFSET_SHIFT;
+ reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ;
+ writel(reg_val, sid->base + SUN8I_SID_PRCTL);
+
+ ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val,
+ !(reg_val & SUN8I_SID_READ), 100, 250000);
+ if (ret)
+ return ret;
+
+ writel(0, sid->base + SUN8I_SID_PRCTL);
+ return 0;
+}
+
static int sunxi_sid_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -72,18 +114,42 @@ static int sunxi_sid_probe(struct platform_device *pdev)
struct sunxi_sid *sid;
int ret, i, size;
char *randomness;
+ const struct sunxi_sid_cfg *cfg;
sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL);
if (!sid)
return -ENOMEM;
+ cfg = of_device_get_match_data(dev);
+ if (!cfg)
+ return -EINVAL;
+ sid->value_offset = cfg->value_offset;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sid->base = devm_ioremap_resource(dev, res);
if (IS_ERR(sid->base))
return PTR_ERR(sid->base);
- size = resource_size(res) - 1;
- econfig.size = resource_size(res);
+ size = cfg->size;
+
+ if (cfg->need_register_readout) {
+ /*
+ * H3's SID controller have a bug that the value at 0x200
+ * offset is not the correct value when the hardware is reseted.
+ * However, after doing a register-based read operation, the
+ * value become right.
+ * Do a full read operation here, but ignore its value
+ * (as it's more fast to read by direct MMIO value than
+ * with registers)
+ */
+ for (i = 0; i < (size >> 2); i++) {
+ ret = sun8i_sid_register_readout(sid, i);
+ if (ret)
+ return ret;
+ }
+ }
+
+ econfig.size = size;
econfig.dev = dev;
econfig.reg_read = sunxi_sid_read;
econfig.priv = sid;
@@ -119,9 +185,24 @@ static int sunxi_sid_remove(struct platform_device *pdev)
return nvmem_unregister(nvmem);
}
+static const struct sunxi_sid_cfg sun4i_a10_cfg = {
+ .size = 0x10,
+};
+
+static const struct sunxi_sid_cfg sun7i_a20_cfg = {
+ .size = 0x200,
+};
+
+static const struct sunxi_sid_cfg sun8i_h3_cfg = {
+ .value_offset = 0x200,
+ .size = 0x100,
+ .need_register_readout = true,
+};
+
static const struct of_device_id sunxi_sid_of_match[] = {
- { .compatible = "allwinner,sun4i-a10-sid" },
- { .compatible = "allwinner,sun7i-a20-sid" },
+ { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg },
+ { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
+ { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
{/* sentinel */},
};
MODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c
index 6f09da4dadb8..6aa120cd0574 100644
--- a/drivers/platform/chrome/cros_ec_dev.c
+++ b/drivers/platform/chrome/cros_ec_dev.c
@@ -391,7 +391,6 @@ static int ec_device_probe(struct platform_device *pdev)
int retval = -ENOMEM;
struct device *dev = &pdev->dev;
struct cros_ec_platform *ec_platform = dev_get_platdata(dev);
- dev_t devno = MKDEV(ec_major, pdev->id);
struct cros_ec_dev *ec = kzalloc(sizeof(*ec), GFP_KERNEL);
if (!ec)
@@ -407,23 +406,11 @@ static int ec_device_probe(struct platform_device *pdev)
cdev_init(&ec->cdev, &fops);
/*
- * Add the character device
- * Link cdev to the class device to be sure device is not used
- * before unbinding it.
- */
- ec->cdev.kobj.parent = &ec->class_dev.kobj;
- retval = cdev_add(&ec->cdev, devno, 1);
- if (retval) {
- dev_err(dev, ": failed to add character device\n");
- goto cdev_add_failed;
- }
-
- /*
* Add the class device
* Link to the character device for creating the /dev entry
* in devtmpfs.
*/
- ec->class_dev.devt = ec->cdev.dev;
+ ec->class_dev.devt = MKDEV(ec_major, pdev->id);
ec->class_dev.class = &cros_class;
ec->class_dev.parent = dev;
ec->class_dev.release = __remove;
@@ -431,13 +418,13 @@ static int ec_device_probe(struct platform_device *pdev)
retval = dev_set_name(&ec->class_dev, "%s", ec_platform->ec_name);
if (retval) {
dev_err(dev, "dev_set_name failed => %d\n", retval);
- goto set_named_failed;
+ goto failed;
}
- retval = device_add(&ec->class_dev);
+ retval = cdev_device_add(&ec->cdev, &ec->class_dev);
if (retval) {
- dev_err(dev, "device_register failed => %d\n", retval);
- goto dev_reg_failed;
+ dev_err(dev, "cdev_device_add failed => %d\n", retval);
+ goto failed;
}
/* check whether this EC is a sensor hub. */
@@ -446,12 +433,8 @@ static int ec_device_probe(struct platform_device *pdev)
return 0;
-dev_reg_failed:
-set_named_failed:
- dev_set_drvdata(dev, NULL);
- cdev_del(&ec->cdev);
-cdev_add_failed:
- kfree(ec);
+failed:
+ put_device(&ec->class_dev);
return retval;
}
diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c
index 2b21033f11f0..2de1e603bd2b 100644
--- a/drivers/platform/goldfish/goldfish_pipe.c
+++ b/drivers/platform/goldfish/goldfish_pipe.c
@@ -1,8 +1,8 @@
/*
- * Copyright (C) 2011 Google, Inc.
* Copyright (C) 2012 Intel, Inc.
* Copyright (C) 2013 Intel, Inc.
* Copyright (C) 2014 Linaro Limited
+ * Copyright (C) 2011-2016 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -46,6 +46,7 @@
* exchange is properly mapped during a transfer.
*/
+
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -63,122 +64,232 @@
#include <linux/acpi.h>
/*
+ * Update this when something changes in the driver's behavior so the host
+ * can benefit from knowing it
+ */
+enum {
+ PIPE_DRIVER_VERSION = 2,
+ PIPE_CURRENT_DEVICE_VERSION = 2
+};
+
+/*
* IMPORTANT: The following constants must match the ones used and defined
* in external/qemu/hw/goldfish_pipe.c in the Android source tree.
*/
-/* pipe device registers */
-#define PIPE_REG_COMMAND 0x00 /* write: value = command */
-#define PIPE_REG_STATUS 0x04 /* read */
-#define PIPE_REG_CHANNEL 0x08 /* read/write: channel id */
-#define PIPE_REG_CHANNEL_HIGH 0x30 /* read/write: channel id */
-#define PIPE_REG_SIZE 0x0c /* read/write: buffer size */
-#define PIPE_REG_ADDRESS 0x10 /* write: physical address */
-#define PIPE_REG_ADDRESS_HIGH 0x34 /* write: physical address */
-#define PIPE_REG_WAKES 0x14 /* read: wake flags */
-#define PIPE_REG_PARAMS_ADDR_LOW 0x18 /* read/write: batch data address */
-#define PIPE_REG_PARAMS_ADDR_HIGH 0x1c /* read/write: batch data address */
-#define PIPE_REG_ACCESS_PARAMS 0x20 /* write: batch access */
-#define PIPE_REG_VERSION 0x24 /* read: device version */
-
-/* list of commands for PIPE_REG_COMMAND */
-#define CMD_OPEN 1 /* open new channel */
-#define CMD_CLOSE 2 /* close channel (from guest) */
-#define CMD_POLL 3 /* poll read/write status */
-
/* List of bitflags returned in status of CMD_POLL command */
-#define PIPE_POLL_IN (1 << 0)
-#define PIPE_POLL_OUT (1 << 1)
-#define PIPE_POLL_HUP (1 << 2)
-
-/* The following commands are related to write operations */
-#define CMD_WRITE_BUFFER 4 /* send a user buffer to the emulator */
-#define CMD_WAKE_ON_WRITE 5 /* tell the emulator to wake us when writing
- is possible */
-#define CMD_READ_BUFFER 6 /* receive a user buffer from the emulator */
-#define CMD_WAKE_ON_READ 7 /* tell the emulator to wake us when reading
- * is possible */
+enum PipePollFlags {
+ PIPE_POLL_IN = 1 << 0,
+ PIPE_POLL_OUT = 1 << 1,
+ PIPE_POLL_HUP = 1 << 2
+};
/* Possible status values used to signal errors - see goldfish_pipe_error_convert */
-#define PIPE_ERROR_INVAL -1
-#define PIPE_ERROR_AGAIN -2
-#define PIPE_ERROR_NOMEM -3
-#define PIPE_ERROR_IO -4
+enum PipeErrors {
+ PIPE_ERROR_INVAL = -1,
+ PIPE_ERROR_AGAIN = -2,
+ PIPE_ERROR_NOMEM = -3,
+ PIPE_ERROR_IO = -4
+};
/* Bit-flags used to signal events from the emulator */
-#define PIPE_WAKE_CLOSED (1 << 0) /* emulator closed pipe */
-#define PIPE_WAKE_READ (1 << 1) /* pipe can now be read from */
-#define PIPE_WAKE_WRITE (1 << 2) /* pipe can now be written to */
-
-struct access_params {
- unsigned long channel;
- u32 size;
- unsigned long address;
- u32 cmd;
- u32 result;
- /* reserved for future extension */
+enum PipeWakeFlags {
+ PIPE_WAKE_CLOSED = 1 << 0, /* emulator closed pipe */
+ PIPE_WAKE_READ = 1 << 1, /* pipe can now be read from */
+ PIPE_WAKE_WRITE = 1 << 2 /* pipe can now be written to */
+};
+
+/* Bit flags for the 'flags' field */
+enum PipeFlagsBits {
+ BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */
+ BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */
+ BIT_WAKE_ON_READ = 2, /* want to be woken on reads */
+};
+
+enum PipeRegs {
+ PIPE_REG_CMD = 0,
+
+ PIPE_REG_SIGNAL_BUFFER_HIGH = 4,
+ PIPE_REG_SIGNAL_BUFFER = 8,
+ PIPE_REG_SIGNAL_BUFFER_COUNT = 12,
+
+ PIPE_REG_OPEN_BUFFER_HIGH = 20,
+ PIPE_REG_OPEN_BUFFER = 24,
+
+ PIPE_REG_VERSION = 36,
+
+ PIPE_REG_GET_SIGNALLED = 48,
+};
+
+enum PipeCmdCode {
+ PIPE_CMD_OPEN = 1, /* to be used by the pipe device itself */
+ PIPE_CMD_CLOSE,
+ PIPE_CMD_POLL,
+ PIPE_CMD_WRITE,
+ PIPE_CMD_WAKE_ON_WRITE,
+ PIPE_CMD_READ,
+ PIPE_CMD_WAKE_ON_READ,
+
+ /*
+ * TODO(zyy): implement a deferred read/write execution to allow
+ * parallel processing of pipe operations on the host.
+ */
+ PIPE_CMD_WAKE_ON_DONE_IO,
+};
+
+enum {
+ MAX_BUFFERS_PER_COMMAND = 336,
+ MAX_SIGNALLED_PIPES = 64,
+ INITIAL_PIPES_CAPACITY = 64
+};
+
+struct goldfish_pipe_dev;
+struct goldfish_pipe;
+struct goldfish_pipe_command;
+
+/* A per-pipe command structure, shared with the host */
+struct goldfish_pipe_command {
+ s32 cmd; /* PipeCmdCode, guest -> host */
+ s32 id; /* pipe id, guest -> host */
+ s32 status; /* command execution status, host -> guest */
+ s32 reserved; /* to pad to 64-bit boundary */
+ union {
+ /* Parameters for PIPE_CMD_{READ,WRITE} */
+ struct {
+ /* number of buffers, guest -> host */
+ u32 buffers_count;
+ /* number of consumed bytes, host -> guest */
+ s32 consumed_size;
+ /* buffer pointers, guest -> host */
+ u64 ptrs[MAX_BUFFERS_PER_COMMAND];
+ /* buffer sizes, guest -> host */
+ u32 sizes[MAX_BUFFERS_PER_COMMAND];
+ } rw_params;
+ };
+};
+
+/* A single signalled pipe information */
+struct signalled_pipe_buffer {
+ u32 id;
u32 flags;
};
-/* The global driver data. Holds a reference to the i/o page used to
- * communicate with the emulator, and a wake queue for blocked tasks
- * waiting to be awoken.
- */
-struct goldfish_pipe_dev {
- spinlock_t lock;
- unsigned char __iomem *base;
- struct access_params *aps;
- int irq;
- u32 version;
+/* Parameters for the PIPE_CMD_OPEN command */
+struct open_command_param {
+ u64 command_buffer_ptr;
+ u32 rw_params_max_count;
};
-static struct goldfish_pipe_dev pipe_dev[1];
+/* Device-level set of buffers shared with the host */
+struct goldfish_pipe_dev_buffers {
+ struct open_command_param open_command_params;
+ struct signalled_pipe_buffer signalled_pipe_buffers[
+ MAX_SIGNALLED_PIPES];
+};
/* This data type models a given pipe instance */
struct goldfish_pipe {
- struct goldfish_pipe_dev *dev;
- struct mutex lock;
+ /* pipe ID - index into goldfish_pipe_dev::pipes array */
+ u32 id;
+ /* The wake flags pipe is waiting for
+ * Note: not protected with any lock, uses atomic operations
+ * and barriers to make it thread-safe.
+ */
unsigned long flags;
+ /* wake flags host have signalled,
+ * - protected by goldfish_pipe_dev::lock
+ */
+ unsigned long signalled_flags;
+
+ /* A pointer to command buffer */
+ struct goldfish_pipe_command *command_buffer;
+
+ /* doubly linked list of signalled pipes, protected by
+ * goldfish_pipe_dev::lock
+ */
+ struct goldfish_pipe *prev_signalled;
+ struct goldfish_pipe *next_signalled;
+
+ /*
+ * A pipe's own lock. Protects the following:
+ * - *command_buffer - makes sure a command can safely write its
+ * parameters to the host and read the results back.
+ */
+ struct mutex lock;
+
+ /* A wake queue for sleeping until host signals an event */
wait_queue_head_t wake_queue;
+ /* Pointer to the parent goldfish_pipe_dev instance */
+ struct goldfish_pipe_dev *dev;
};
+/* The global driver data. Holds a reference to the i/o page used to
+ * communicate with the emulator, and a wake queue for blocked tasks
+ * waiting to be awoken.
+ */
+struct goldfish_pipe_dev {
+ /*
+ * Global device spinlock. Protects the following members:
+ * - pipes, pipes_capacity
+ * - [*pipes, *pipes + pipes_capacity) - array data
+ * - first_signalled_pipe,
+ * goldfish_pipe::prev_signalled,
+ * goldfish_pipe::next_signalled,
+ * goldfish_pipe::signalled_flags - all singnalled-related fields,
+ * in all allocated pipes
+ * - open_command_params - PIPE_CMD_OPEN-related buffers
+ *
+ * It looks like a lot of different fields, but the trick is that
+ * the only operation that happens often is the signalled pipes array
+ * manipulation. That's why it's OK for now to keep the rest of the
+ * fields under the same lock. If we notice too much contention because
+ * of PIPE_CMD_OPEN, then we should add a separate lock there.
+ */
+ spinlock_t lock;
-/* Bit flags for the 'flags' field */
-enum {
- BIT_CLOSED_ON_HOST = 0, /* pipe closed by host */
- BIT_WAKE_ON_WRITE = 1, /* want to be woken on writes */
- BIT_WAKE_ON_READ = 2, /* want to be woken on reads */
+ /*
+ * Array of the pipes of |pipes_capacity| elements,
+ * indexed by goldfish_pipe::id
+ */
+ struct goldfish_pipe **pipes;
+ u32 pipes_capacity;
+
+ /* Pointers to the buffers host uses for interaction with this driver */
+ struct goldfish_pipe_dev_buffers *buffers;
+
+ /* Head of a doubly linked list of signalled pipes */
+ struct goldfish_pipe *first_signalled_pipe;
+
+ /* Some device-specific data */
+ int irq;
+ int version;
+ unsigned char __iomem *base;
};
+struct goldfish_pipe_dev pipe_dev[1] = {};
-static u32 goldfish_cmd_status(struct goldfish_pipe *pipe, u32 cmd)
+static int goldfish_cmd_locked(struct goldfish_pipe *pipe, enum PipeCmdCode cmd)
{
- unsigned long flags;
- u32 status;
- struct goldfish_pipe_dev *dev = pipe->dev;
-
- spin_lock_irqsave(&dev->lock, flags);
- gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
- writel(cmd, dev->base + PIPE_REG_COMMAND);
- status = readl(dev->base + PIPE_REG_STATUS);
- spin_unlock_irqrestore(&dev->lock, flags);
- return status;
+ pipe->command_buffer->cmd = cmd;
+ /* failure by default */
+ pipe->command_buffer->status = PIPE_ERROR_INVAL;
+ writel(pipe->id, pipe->dev->base + PIPE_REG_CMD);
+ return pipe->command_buffer->status;
}
-static void goldfish_cmd(struct goldfish_pipe *pipe, u32 cmd)
+static int goldfish_cmd(struct goldfish_pipe *pipe, enum PipeCmdCode cmd)
{
- unsigned long flags;
- struct goldfish_pipe_dev *dev = pipe->dev;
+ int status;
- spin_lock_irqsave(&dev->lock, flags);
- gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
- writel(cmd, dev->base + PIPE_REG_COMMAND);
- spin_unlock_irqrestore(&dev->lock, flags);
+ if (mutex_lock_interruptible(&pipe->lock))
+ return PIPE_ERROR_IO;
+ status = goldfish_cmd_locked(pipe, cmd);
+ mutex_unlock(&pipe->lock);
+ return status;
}
-/* This function converts an error code returned by the emulator through
+/*
+ * This function converts an error code returned by the emulator through
* the PIPE_REG_STATUS i/o register into a valid negative errno value.
*/
static int goldfish_pipe_error_convert(int status)
@@ -195,184 +306,202 @@ static int goldfish_pipe_error_convert(int status)
}
}
-/*
- * Notice: QEMU will return 0 for un-known register access, indicating
- * param_acess is supported or not
- */
-static int valid_batchbuffer_addr(struct goldfish_pipe_dev *dev,
- struct access_params *aps)
+static int pin_user_pages(unsigned long first_page, unsigned long last_page,
+ unsigned int last_page_size, int is_write,
+ struct page *pages[MAX_BUFFERS_PER_COMMAND],
+ unsigned int *iter_last_page_size)
{
- u32 aph, apl;
- u64 paddr;
- aph = readl(dev->base + PIPE_REG_PARAMS_ADDR_HIGH);
- apl = readl(dev->base + PIPE_REG_PARAMS_ADDR_LOW);
+ int ret;
+ int requested_pages = ((last_page - first_page) >> PAGE_SHIFT) + 1;
+
+ if (requested_pages > MAX_BUFFERS_PER_COMMAND) {
+ requested_pages = MAX_BUFFERS_PER_COMMAND;
+ *iter_last_page_size = PAGE_SIZE;
+ } else {
+ *iter_last_page_size = last_page_size;
+ }
+
+ ret = get_user_pages_fast(
+ first_page, requested_pages, !is_write, pages);
+ if (ret <= 0)
+ return -EFAULT;
+ if (ret < requested_pages)
+ *iter_last_page_size = PAGE_SIZE;
+ return ret;
- paddr = ((u64)aph << 32) | apl;
- if (paddr != (__pa(aps)))
- return 0;
- return 1;
}
-/* 0 on success */
-static int setup_access_params_addr(struct platform_device *pdev,
- struct goldfish_pipe_dev *dev)
+static void release_user_pages(struct page **pages, int pages_count,
+ int is_write, s32 consumed_size)
{
- dma_addr_t dma_handle;
- struct access_params *aps;
+ int i;
- aps = dmam_alloc_coherent(&pdev->dev, sizeof(struct access_params),
- &dma_handle, GFP_KERNEL);
- if (!aps)
- return -ENOMEM;
+ for (i = 0; i < pages_count; i++) {
+ if (!is_write && consumed_size > 0)
+ set_page_dirty(pages[i]);
+ put_page(pages[i]);
+ }
+}
+
+/* Populate the call parameters, merging adjacent pages together */
+static void populate_rw_params(
+ struct page **pages, int pages_count,
+ unsigned long address, unsigned long address_end,
+ unsigned long first_page, unsigned long last_page,
+ unsigned int iter_last_page_size, int is_write,
+ struct goldfish_pipe_command *command)
+{
+ /*
+ * Process the first page separately - it's the only page that
+ * needs special handling for its start address.
+ */
+ unsigned long xaddr = page_to_phys(pages[0]);
+ unsigned long xaddr_prev = xaddr;
+ int buffer_idx = 0;
+ int i = 1;
+ int size_on_page = first_page == last_page
+ ? (int)(address_end - address)
+ : (PAGE_SIZE - (address & ~PAGE_MASK));
+ command->rw_params.ptrs[0] = (u64)(xaddr | (address & ~PAGE_MASK));
+ command->rw_params.sizes[0] = size_on_page;
+ for (; i < pages_count; ++i) {
+ xaddr = page_to_phys(pages[i]);
+ size_on_page = (i == pages_count - 1) ?
+ iter_last_page_size : PAGE_SIZE;
+ if (xaddr == xaddr_prev + PAGE_SIZE) {
+ command->rw_params.sizes[buffer_idx] += size_on_page;
+ } else {
+ ++buffer_idx;
+ command->rw_params.ptrs[buffer_idx] = (u64)xaddr;
+ command->rw_params.sizes[buffer_idx] = size_on_page;
+ }
+ xaddr_prev = xaddr;
+ }
+ command->rw_params.buffers_count = buffer_idx + 1;
+}
- writel(upper_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_HIGH);
- writel(lower_32_bits(dma_handle), dev->base + PIPE_REG_PARAMS_ADDR_LOW);
+static int transfer_max_buffers(struct goldfish_pipe *pipe,
+ unsigned long address, unsigned long address_end, int is_write,
+ unsigned long last_page, unsigned int last_page_size,
+ s32 *consumed_size, int *status)
+{
+ static struct page *pages[MAX_BUFFERS_PER_COMMAND];
+ unsigned long first_page = address & PAGE_MASK;
+ unsigned int iter_last_page_size;
+ int pages_count = pin_user_pages(first_page, last_page,
+ last_page_size, is_write,
+ pages, &iter_last_page_size);
- if (valid_batchbuffer_addr(dev, aps)) {
- dev->aps = aps;
- return 0;
- } else
- return -1;
+ if (pages_count < 0)
+ return pages_count;
+
+ /* Serialize access to the pipe command buffers */
+ if (mutex_lock_interruptible(&pipe->lock))
+ return -ERESTARTSYS;
+
+ populate_rw_params(pages, pages_count, address, address_end,
+ first_page, last_page, iter_last_page_size, is_write,
+ pipe->command_buffer);
+
+ /* Transfer the data */
+ *status = goldfish_cmd_locked(pipe,
+ is_write ? PIPE_CMD_WRITE : PIPE_CMD_READ);
+
+ *consumed_size = pipe->command_buffer->rw_params.consumed_size;
+
+ release_user_pages(pages, pages_count, is_write, *consumed_size);
+
+ mutex_unlock(&pipe->lock);
+
+ return 0;
}
-/* A value that will not be set by qemu emulator */
-#define INITIAL_BATCH_RESULT (0xdeadbeaf)
-static int access_with_param(struct goldfish_pipe_dev *dev, const int cmd,
- unsigned long address, unsigned long avail,
- struct goldfish_pipe *pipe, int *status)
+static int wait_for_host_signal(struct goldfish_pipe *pipe, int is_write)
{
- struct access_params *aps = dev->aps;
+ u32 wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ;
- if (aps == NULL)
- return -1;
+ set_bit(wakeBit, &pipe->flags);
+
+ /* Tell the emulator we're going to wait for a wake event */
+ (void)goldfish_cmd(pipe,
+ is_write ? PIPE_CMD_WAKE_ON_WRITE : PIPE_CMD_WAKE_ON_READ);
+
+ while (test_bit(wakeBit, &pipe->flags)) {
+ if (wait_event_interruptible(
+ pipe->wake_queue,
+ !test_bit(wakeBit, &pipe->flags)))
+ return -ERESTARTSYS;
+
+ if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
+ return -EIO;
+ }
- aps->result = INITIAL_BATCH_RESULT;
- aps->channel = (unsigned long)pipe;
- aps->size = avail;
- aps->address = address;
- aps->cmd = cmd;
- writel(cmd, dev->base + PIPE_REG_ACCESS_PARAMS);
- /*
- * If the aps->result has not changed, that means
- * that the batch command failed
- */
- if (aps->result == INITIAL_BATCH_RESULT)
- return -1;
- *status = aps->result;
return 0;
}
-static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
- size_t bufflen, int is_write)
+static ssize_t goldfish_pipe_read_write(struct file *filp,
+ char __user *buffer, size_t bufflen, int is_write)
{
- unsigned long irq_flags;
struct goldfish_pipe *pipe = filp->private_data;
- struct goldfish_pipe_dev *dev = pipe->dev;
- unsigned long address, address_end;
int count = 0, ret = -EINVAL;
+ unsigned long address, address_end, last_page;
+ unsigned int last_page_size;
/* If the emulator already closed the pipe, no need to go further */
- if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
+ if (unlikely(test_bit(BIT_CLOSED_ON_HOST, &pipe->flags)))
return -EIO;
-
/* Null reads or writes succeeds */
if (unlikely(bufflen == 0))
return 0;
-
/* Check the buffer range for access */
- if (!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ,
- buffer, bufflen))
+ if (unlikely(!access_ok(is_write ? VERIFY_WRITE : VERIFY_READ,
+ buffer, bufflen)))
return -EFAULT;
- /* Serialize access to the pipe */
- if (mutex_lock_interruptible(&pipe->lock))
- return -ERESTARTSYS;
-
- address = (unsigned long)(void *)buffer;
+ address = (unsigned long)buffer;
address_end = address + bufflen;
+ last_page = (address_end - 1) & PAGE_MASK;
+ last_page_size = ((address_end - 1) & ~PAGE_MASK) + 1;
while (address < address_end) {
- unsigned long page_end = (address & PAGE_MASK) + PAGE_SIZE;
- unsigned long next = page_end < address_end ? page_end
- : address_end;
- unsigned long avail = next - address;
- int status, wakeBit;
- struct page *page;
-
- /* Either vaddr or paddr depending on the device version */
- unsigned long xaddr;
+ s32 consumed_size;
+ int status;
- /*
- * We grab the pages on a page-by-page basis in case user
- * space gives us a potentially huge buffer but the read only
- * returns a small amount, then there's no need to pin that
- * much memory to the process.
- */
- ret = get_user_pages_unlocked(address, 1, &page,
- is_write ? 0 : FOLL_WRITE);
+ ret = transfer_max_buffers(pipe, address, address_end, is_write,
+ last_page, last_page_size, &consumed_size,
+ &status);
if (ret < 0)
break;
- if (dev->version) {
- /* Device version 1 or newer (qemu-android) expects the
- * physical address.
+ if (consumed_size > 0) {
+ /* No matter what's the status, we've transferred
+ * something.
*/
- xaddr = page_to_phys(page) | (address & ~PAGE_MASK);
- } else {
- /* Device version 0 (classic emulator) expects the
- * virtual address.
- */
- xaddr = address;
+ count += consumed_size;
+ address += consumed_size;
}
-
- /* Now, try to transfer the bytes in the current page */
- spin_lock_irqsave(&dev->lock, irq_flags);
- if (access_with_param(dev,
- is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER,
- xaddr, avail, pipe, &status)) {
- gf_write_ptr(pipe, dev->base + PIPE_REG_CHANNEL,
- dev->base + PIPE_REG_CHANNEL_HIGH);
- writel(avail, dev->base + PIPE_REG_SIZE);
- gf_write_ptr((void *)xaddr,
- dev->base + PIPE_REG_ADDRESS,
- dev->base + PIPE_REG_ADDRESS_HIGH);
- writel(is_write ? CMD_WRITE_BUFFER : CMD_READ_BUFFER,
- dev->base + PIPE_REG_COMMAND);
- status = readl(dev->base + PIPE_REG_STATUS);
- }
- spin_unlock_irqrestore(&dev->lock, irq_flags);
-
- if (status > 0 && !is_write)
- set_page_dirty(page);
- put_page(page);
-
- if (status > 0) { /* Correct transfer */
- count += status;
- address += status;
+ if (status > 0)
continue;
- } else if (status == 0) { /* EOF */
+ if (status == 0) {
+ /* EOF */
ret = 0;
break;
- } else if (status < 0 && count > 0) {
+ }
+ if (count > 0) {
/*
- * An error occurred and we already transferred
- * something on one of the previous pages.
+ * An error occurred, but we already transferred
+ * something on one of the previous iterations.
* Just return what we already copied and log this
* err.
- *
- * Note: This seems like an incorrect approach but
- * cannot change it until we check if any user space
- * ABI relies on this behavior.
*/
if (status != PIPE_ERROR_AGAIN)
- pr_info_ratelimited("goldfish_pipe: backend returned error %d on %s\n",
+ pr_info_ratelimited("goldfish_pipe: backend error %d on %s\n",
status, is_write ? "write" : "read");
- ret = 0;
break;
}
/*
- * If the error is not PIPE_ERROR_AGAIN, or if we are not in
+ * If the error is not PIPE_ERROR_AGAIN, or if we are in
* non-blocking mode, just return the error code.
*/
if (status != PIPE_ERROR_AGAIN ||
@@ -381,139 +510,214 @@ static ssize_t goldfish_pipe_read_write(struct file *filp, char __user *buffer,
break;
}
- /*
- * The backend blocked the read/write, wait until the backend
- * tells us it's ready to process more data.
- */
- wakeBit = is_write ? BIT_WAKE_ON_WRITE : BIT_WAKE_ON_READ;
- set_bit(wakeBit, &pipe->flags);
-
- /* Tell the emulator we're going to wait for a wake event */
- goldfish_cmd(pipe,
- is_write ? CMD_WAKE_ON_WRITE : CMD_WAKE_ON_READ);
-
- /* Unlock the pipe, then wait for the wake signal */
- mutex_unlock(&pipe->lock);
-
- while (test_bit(wakeBit, &pipe->flags)) {
- if (wait_event_interruptible(
- pipe->wake_queue,
- !test_bit(wakeBit, &pipe->flags)))
- return -ERESTARTSYS;
-
- if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
- return -EIO;
- }
-
- /* Try to re-acquire the lock */
- if (mutex_lock_interruptible(&pipe->lock))
- return -ERESTARTSYS;
+ status = wait_for_host_signal(pipe, is_write);
+ if (status < 0)
+ return status;
}
- mutex_unlock(&pipe->lock);
- if (ret < 0)
- return ret;
- else
+ if (count > 0)
return count;
+ return ret;
}
static ssize_t goldfish_pipe_read(struct file *filp, char __user *buffer,
- size_t bufflen, loff_t *ppos)
+ size_t bufflen, loff_t *ppos)
{
- return goldfish_pipe_read_write(filp, buffer, bufflen, 0);
+ return goldfish_pipe_read_write(filp, buffer, bufflen,
+ /* is_write */ 0);
}
static ssize_t goldfish_pipe_write(struct file *filp,
const char __user *buffer, size_t bufflen,
loff_t *ppos)
{
- return goldfish_pipe_read_write(filp, (char __user *)buffer,
- bufflen, 1);
+ return goldfish_pipe_read_write(filp,
+ /* cast away the const */(char __user *)buffer, bufflen,
+ /* is_write */ 1);
}
-
static unsigned int goldfish_pipe_poll(struct file *filp, poll_table *wait)
{
struct goldfish_pipe *pipe = filp->private_data;
unsigned int mask = 0;
int status;
- mutex_lock(&pipe->lock);
-
poll_wait(filp, &pipe->wake_queue, wait);
- status = goldfish_cmd_status(pipe, CMD_POLL);
-
- mutex_unlock(&pipe->lock);
+ status = goldfish_cmd(pipe, PIPE_CMD_POLL);
+ if (status < 0)
+ return -ERESTARTSYS;
if (status & PIPE_POLL_IN)
mask |= POLLIN | POLLRDNORM;
-
if (status & PIPE_POLL_OUT)
mask |= POLLOUT | POLLWRNORM;
-
if (status & PIPE_POLL_HUP)
mask |= POLLHUP;
-
if (test_bit(BIT_CLOSED_ON_HOST, &pipe->flags))
mask |= POLLERR;
return mask;
}
-static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id)
+static void signalled_pipes_add_locked(struct goldfish_pipe_dev *dev,
+ u32 id, u32 flags)
{
- struct goldfish_pipe_dev *dev = dev_id;
- unsigned long irq_flags;
- int count = 0;
+ struct goldfish_pipe *pipe;
- /*
- * We're going to read from the emulator a list of (channel,flags)
- * pairs corresponding to the wake events that occurred on each
- * blocked pipe (i.e. channel).
- */
- spin_lock_irqsave(&dev->lock, irq_flags);
- for (;;) {
- /* First read the channel, 0 means the end of the list */
- struct goldfish_pipe *pipe;
- unsigned long wakes;
- unsigned long channel = 0;
+ if (WARN_ON(id >= dev->pipes_capacity))
+ return;
+
+ pipe = dev->pipes[id];
+ if (!pipe)
+ return;
+ pipe->signalled_flags |= flags;
+
+ if (pipe->prev_signalled || pipe->next_signalled
+ || dev->first_signalled_pipe == pipe)
+ return; /* already in the list */
+ pipe->next_signalled = dev->first_signalled_pipe;
+ if (dev->first_signalled_pipe)
+ dev->first_signalled_pipe->prev_signalled = pipe;
+ dev->first_signalled_pipe = pipe;
+}
-#ifdef CONFIG_64BIT
- channel = (u64)readl(dev->base + PIPE_REG_CHANNEL_HIGH) << 32;
+static void signalled_pipes_remove_locked(struct goldfish_pipe_dev *dev,
+ struct goldfish_pipe *pipe) {
+ if (pipe->prev_signalled)
+ pipe->prev_signalled->next_signalled = pipe->next_signalled;
+ if (pipe->next_signalled)
+ pipe->next_signalled->prev_signalled = pipe->prev_signalled;
+ if (pipe == dev->first_signalled_pipe)
+ dev->first_signalled_pipe = pipe->next_signalled;
+ pipe->prev_signalled = NULL;
+ pipe->next_signalled = NULL;
+}
- if (channel == 0)
- break;
-#endif
- channel |= readl(dev->base + PIPE_REG_CHANNEL);
+static struct goldfish_pipe *signalled_pipes_pop_front(
+ struct goldfish_pipe_dev *dev, int *wakes)
+{
+ struct goldfish_pipe *pipe;
+ unsigned long flags;
- if (channel == 0)
- break;
+ spin_lock_irqsave(&dev->lock, flags);
- /* Convert channel to struct pipe pointer + read wake flags */
- wakes = readl(dev->base + PIPE_REG_WAKES);
- pipe = (struct goldfish_pipe *)(ptrdiff_t)channel;
+ pipe = dev->first_signalled_pipe;
+ if (pipe) {
+ *wakes = pipe->signalled_flags;
+ pipe->signalled_flags = 0;
+ /*
+ * This is an optimized version of
+ * signalled_pipes_remove_locked()
+ * - We want to make it as fast as possible to
+ * wake the sleeping pipe operations faster.
+ */
+ dev->first_signalled_pipe = pipe->next_signalled;
+ if (dev->first_signalled_pipe)
+ dev->first_signalled_pipe->prev_signalled = NULL;
+ pipe->next_signalled = NULL;
+ }
- /* Did the emulator just closed a pipe? */
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return pipe;
+}
+
+static void goldfish_interrupt_task(unsigned long unused)
+{
+ struct goldfish_pipe_dev *dev = pipe_dev;
+ /* Iterate over the signalled pipes and wake them one by one */
+ struct goldfish_pipe *pipe;
+ int wakes;
+
+ while ((pipe = signalled_pipes_pop_front(dev, &wakes)) != NULL) {
if (wakes & PIPE_WAKE_CLOSED) {
- set_bit(BIT_CLOSED_ON_HOST, &pipe->flags);
- wakes |= PIPE_WAKE_READ | PIPE_WAKE_WRITE;
+ pipe->flags = 1 << BIT_CLOSED_ON_HOST;
+ } else {
+ if (wakes & PIPE_WAKE_READ)
+ clear_bit(BIT_WAKE_ON_READ, &pipe->flags);
+ if (wakes & PIPE_WAKE_WRITE)
+ clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags);
}
- if (wakes & PIPE_WAKE_READ)
- clear_bit(BIT_WAKE_ON_READ, &pipe->flags);
- if (wakes & PIPE_WAKE_WRITE)
- clear_bit(BIT_WAKE_ON_WRITE, &pipe->flags);
-
+ /*
+ * wake_up_interruptible() implies a write barrier, so don't
+ * explicitly add another one here.
+ */
wake_up_interruptible(&pipe->wake_queue);
- count++;
}
- spin_unlock_irqrestore(&dev->lock, irq_flags);
+}
+DECLARE_TASKLET(goldfish_interrupt_tasklet, goldfish_interrupt_task, 0);
- return (count == 0) ? IRQ_NONE : IRQ_HANDLED;
+/*
+ * The general idea of the interrupt handling:
+ *
+ * 1. device raises an interrupt if there's at least one signalled pipe
+ * 2. IRQ handler reads the signalled pipes and their count from the device
+ * 3. device writes them into a shared buffer and returns the count
+ * it only resets the IRQ if it has returned all signalled pipes,
+ * otherwise it leaves it raised, so IRQ handler will be called
+ * again for the next chunk
+ * 4. IRQ handler adds all returned pipes to the device's signalled pipes list
+ * 5. IRQ handler launches a tasklet to process the signalled pipes from the
+ * list in a separate context
+ */
+static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id)
+{
+ u32 count;
+ u32 i;
+ unsigned long flags;
+ struct goldfish_pipe_dev *dev = dev_id;
+
+ if (dev != pipe_dev)
+ return IRQ_NONE;
+
+ /* Request the signalled pipes from the device */
+ spin_lock_irqsave(&dev->lock, flags);
+
+ count = readl(dev->base + PIPE_REG_GET_SIGNALLED);
+ if (count == 0) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return IRQ_NONE;
+ }
+ if (count > MAX_SIGNALLED_PIPES)
+ count = MAX_SIGNALLED_PIPES;
+
+ for (i = 0; i < count; ++i)
+ signalled_pipes_add_locked(dev,
+ dev->buffers->signalled_pipe_buffers[i].id,
+ dev->buffers->signalled_pipe_buffers[i].flags);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ tasklet_schedule(&goldfish_interrupt_tasklet);
+ return IRQ_HANDLED;
+}
+
+static int get_free_pipe_id_locked(struct goldfish_pipe_dev *dev)
+{
+ int id;
+
+ for (id = 0; id < dev->pipes_capacity; ++id)
+ if (!dev->pipes[id])
+ return id;
+
+ {
+ /* Reallocate the array */
+ u32 new_capacity = 2 * dev->pipes_capacity;
+ struct goldfish_pipe **pipes =
+ kcalloc(new_capacity, sizeof(*pipes), GFP_KERNEL);
+ if (!pipes)
+ return -ENOMEM;
+ memcpy(pipes, dev->pipes, sizeof(*pipes) * dev->pipes_capacity);
+ kfree(dev->pipes);
+ dev->pipes = pipes;
+ id = dev->pipes_capacity;
+ dev->pipes_capacity = new_capacity;
+ }
+ return id;
}
/**
- * goldfish_pipe_open - open a channel to the AVD
+ * goldfish_pipe_open - open a channel to the AVD
* @inode: inode of device
* @file: file struct of opener
*
@@ -525,12 +729,13 @@ static irqreturn_t goldfish_pipe_interrupt(int irq, void *dev_id)
*/
static int goldfish_pipe_open(struct inode *inode, struct file *file)
{
- struct goldfish_pipe *pipe;
struct goldfish_pipe_dev *dev = pipe_dev;
- int32_t status;
+ unsigned long flags;
+ int id;
+ int status;
/* Allocate new pipe kernel object */
- pipe = kzalloc(sizeof(*pipe), GFP_KERNEL);
+ struct goldfish_pipe *pipe = kzalloc(sizeof(*pipe), GFP_KERNEL);
if (pipe == NULL)
return -ENOMEM;
@@ -539,29 +744,69 @@ static int goldfish_pipe_open(struct inode *inode, struct file *file)
init_waitqueue_head(&pipe->wake_queue);
/*
- * Now, tell the emulator we're opening a new pipe. We use the
- * pipe object's address as the channel identifier for simplicity.
+ * Command buffer needs to be allocated on its own page to make sure
+ * it is physically contiguous in host's address space.
*/
+ pipe->command_buffer =
+ (struct goldfish_pipe_command *)__get_free_page(GFP_KERNEL);
+ if (!pipe->command_buffer) {
+ status = -ENOMEM;
+ goto err_pipe;
+ }
- status = goldfish_cmd_status(pipe, CMD_OPEN);
- if (status < 0) {
- kfree(pipe);
- return status;
+ spin_lock_irqsave(&dev->lock, flags);
+
+ id = get_free_pipe_id_locked(dev);
+ if (id < 0) {
+ status = id;
+ goto err_id_locked;
}
+ dev->pipes[id] = pipe;
+ pipe->id = id;
+ pipe->command_buffer->id = id;
+
+ /* Now tell the emulator we're opening a new pipe. */
+ dev->buffers->open_command_params.rw_params_max_count =
+ MAX_BUFFERS_PER_COMMAND;
+ dev->buffers->open_command_params.command_buffer_ptr =
+ (u64)(unsigned long)__pa(pipe->command_buffer);
+ status = goldfish_cmd_locked(pipe, PIPE_CMD_OPEN);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ if (status < 0)
+ goto err_cmd;
/* All is done, save the pipe into the file's private data field */
file->private_data = pipe;
return 0;
+
+err_cmd:
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->pipes[id] = NULL;
+err_id_locked:
+ spin_unlock_irqrestore(&dev->lock, flags);
+ free_page((unsigned long)pipe->command_buffer);
+err_pipe:
+ kfree(pipe);
+ return status;
}
static int goldfish_pipe_release(struct inode *inode, struct file *filp)
{
+ unsigned long flags;
struct goldfish_pipe *pipe = filp->private_data;
+ struct goldfish_pipe_dev *dev = pipe->dev;
/* The guest is closing the channel, so tell the emulator right now */
- goldfish_cmd(pipe, CMD_CLOSE);
- kfree(pipe);
+ (void)goldfish_cmd(pipe, PIPE_CMD_CLOSE);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->pipes[pipe->id] = NULL;
+ signalled_pipes_remove_locked(dev, pipe);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
filp->private_data = NULL;
+ free_page((unsigned long)pipe->command_buffer);
+ kfree(pipe);
return 0;
}
@@ -574,18 +819,91 @@ static const struct file_operations goldfish_pipe_fops = {
.release = goldfish_pipe_release,
};
-static struct miscdevice goldfish_pipe_device = {
+static struct miscdevice goldfish_pipe_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "goldfish_pipe",
.fops = &goldfish_pipe_fops,
};
+static int goldfish_pipe_device_init(struct platform_device *pdev)
+{
+ char *page;
+ struct goldfish_pipe_dev *dev = pipe_dev;
+ int err = devm_request_irq(&pdev->dev, dev->irq,
+ goldfish_pipe_interrupt,
+ IRQF_SHARED, "goldfish_pipe", dev);
+ if (err) {
+ dev_err(&pdev->dev, "unable to allocate IRQ for v2\n");
+ return err;
+ }
+
+ err = misc_register(&goldfish_pipe_dev);
+ if (err) {
+ dev_err(&pdev->dev, "unable to register v2 device\n");
+ return err;
+ }
+
+ dev->first_signalled_pipe = NULL;
+ dev->pipes_capacity = INITIAL_PIPES_CAPACITY;
+ dev->pipes = kcalloc(dev->pipes_capacity, sizeof(*dev->pipes),
+ GFP_KERNEL);
+ if (!dev->pipes)
+ return -ENOMEM;
+
+ /*
+ * We're going to pass two buffers, open_command_params and
+ * signalled_pipe_buffers, to the host. This means each of those buffers
+ * needs to be contained in a single physical page. The easiest choice
+ * is to just allocate a page and place the buffers in it.
+ */
+ if (WARN_ON(sizeof(*dev->buffers) > PAGE_SIZE))
+ return -ENOMEM;
+
+ page = (char *)__get_free_page(GFP_KERNEL);
+ if (!page) {
+ kfree(dev->pipes);
+ return -ENOMEM;
+ }
+ dev->buffers = (struct goldfish_pipe_dev_buffers *)page;
+
+ /* Send the buffer addresses to the host */
+ {
+ u64 paddr = __pa(&dev->buffers->signalled_pipe_buffers);
+
+ writel((u32)(unsigned long)(paddr >> 32),
+ dev->base + PIPE_REG_SIGNAL_BUFFER_HIGH);
+ writel((u32)(unsigned long)paddr,
+ dev->base + PIPE_REG_SIGNAL_BUFFER);
+ writel((u32)MAX_SIGNALLED_PIPES,
+ dev->base + PIPE_REG_SIGNAL_BUFFER_COUNT);
+
+ paddr = __pa(&dev->buffers->open_command_params);
+ writel((u32)(unsigned long)(paddr >> 32),
+ dev->base + PIPE_REG_OPEN_BUFFER_HIGH);
+ writel((u32)(unsigned long)paddr,
+ dev->base + PIPE_REG_OPEN_BUFFER);
+ }
+ return 0;
+}
+
+static void goldfish_pipe_device_deinit(struct platform_device *pdev)
+{
+ struct goldfish_pipe_dev *dev = pipe_dev;
+
+ misc_deregister(&goldfish_pipe_dev);
+ kfree(dev->pipes);
+ free_page((unsigned long)dev->buffers);
+}
+
static int goldfish_pipe_probe(struct platform_device *pdev)
{
int err;
struct resource *r;
struct goldfish_pipe_dev *dev = pipe_dev;
+ if (WARN_ON(sizeof(struct goldfish_pipe_command) > PAGE_SIZE))
+ return -ENOMEM;
+
/* not thread safe, but this should not happen */
WARN_ON(dev->base != NULL);
@@ -609,26 +927,21 @@ static int goldfish_pipe_probe(struct platform_device *pdev)
}
dev->irq = r->start;
- err = devm_request_irq(&pdev->dev, dev->irq, goldfish_pipe_interrupt,
- IRQF_SHARED, "goldfish_pipe", dev);
- if (err) {
- dev_err(&pdev->dev, "unable to allocate IRQ\n");
- goto error;
- }
-
- err = misc_register(&goldfish_pipe_device);
- if (err) {
- dev_err(&pdev->dev, "unable to register device\n");
- goto error;
- }
- setup_access_params_addr(pdev, dev);
-
- /* Although the pipe device in the classic Android emulator does not
- * recognize the 'version' register, it won't treat this as an error
- * either and will simply return 0, which is fine.
+ /*
+ * Exchange the versions with the host device
+ *
+ * Note: v1 driver used to not report its version, so we write it before
+ * reading device version back: this allows the host implementation to
+ * detect the old driver (if there was no version write before read).
*/
+ writel((u32)PIPE_DRIVER_VERSION, dev->base + PIPE_REG_VERSION);
dev->version = readl(dev->base + PIPE_REG_VERSION);
- return 0;
+ if (WARN_ON(dev->version < PIPE_CURRENT_DEVICE_VERSION))
+ return -EINVAL;
+
+ err = goldfish_pipe_device_init(pdev);
+ if (!err)
+ return 0;
error:
dev->base = NULL;
@@ -638,7 +951,7 @@ error:
static int goldfish_pipe_remove(struct platform_device *pdev)
{
struct goldfish_pipe_dev *dev = pipe_dev;
- misc_deregister(&goldfish_pipe_device);
+ goldfish_pipe_device_deinit(pdev);
dev->base = NULL;
return 0;
}
@@ -655,17 +968,16 @@ static const struct of_device_id goldfish_pipe_of_match[] = {
};
MODULE_DEVICE_TABLE(of, goldfish_pipe_of_match);
-static struct platform_driver goldfish_pipe = {
+static struct platform_driver goldfish_pipe_driver = {
.probe = goldfish_pipe_probe,
.remove = goldfish_pipe_remove,
.driver = {
.name = "goldfish_pipe",
- .owner = THIS_MODULE,
.of_match_table = goldfish_pipe_of_match,
.acpi_match_table = ACPI_PTR(goldfish_pipe_acpi_match),
}
};
-module_platform_driver(goldfish_pipe);
+module_platform_driver(goldfish_pipe_driver);
MODULE_AUTHOR("David Turner <digit@google.com>");
MODULE_LICENSE("GPL");
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index 2f07cd615665..6eb0db37dd88 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -64,6 +64,43 @@ static int pps_cdev_fasync(int fd, struct file *file, int on)
return fasync_helper(fd, file, on, &pps->async_queue);
}
+static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata)
+{
+ unsigned int ev = pps->last_ev;
+ int err = 0;
+
+ /* Manage the timeout */
+ if (fdata->timeout.flags & PPS_TIME_INVALID)
+ err = wait_event_interruptible(pps->queue,
+ ev != pps->last_ev);
+ else {
+ unsigned long ticks;
+
+ dev_dbg(pps->dev, "timeout %lld.%09d\n",
+ (long long) fdata->timeout.sec,
+ fdata->timeout.nsec);
+ ticks = fdata->timeout.sec * HZ;
+ ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ);
+
+ if (ticks != 0) {
+ err = wait_event_interruptible_timeout(
+ pps->queue,
+ ev != pps->last_ev,
+ ticks);
+ if (err == 0)
+ return -ETIMEDOUT;
+ }
+ }
+
+ /* Check for pending signals */
+ if (err == -ERESTARTSYS) {
+ dev_dbg(pps->dev, "pending signal caught\n");
+ return -EINTR;
+ }
+
+ return 0;
+}
+
static long pps_cdev_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
@@ -144,7 +181,6 @@ static long pps_cdev_ioctl(struct file *file,
case PPS_FETCH: {
struct pps_fdata fdata;
- unsigned int ev;
dev_dbg(pps->dev, "PPS_FETCH\n");
@@ -152,36 +188,9 @@ static long pps_cdev_ioctl(struct file *file,
if (err)
return -EFAULT;
- ev = pps->last_ev;
-
- /* Manage the timeout */
- if (fdata.timeout.flags & PPS_TIME_INVALID)
- err = wait_event_interruptible(pps->queue,
- ev != pps->last_ev);
- else {
- unsigned long ticks;
-
- dev_dbg(pps->dev, "timeout %lld.%09d\n",
- (long long) fdata.timeout.sec,
- fdata.timeout.nsec);
- ticks = fdata.timeout.sec * HZ;
- ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ);
-
- if (ticks != 0) {
- err = wait_event_interruptible_timeout(
- pps->queue,
- ev != pps->last_ev,
- ticks);
- if (err == 0)
- return -ETIMEDOUT;
- }
- }
-
- /* Check for pending signals */
- if (err == -ERESTARTSYS) {
- dev_dbg(pps->dev, "pending signal caught\n");
- return -EINTR;
- }
+ err = pps_cdev_pps_fetch(pps, &fdata);
+ if (err)
+ return err;
/* Return the fetched timestamp */
spin_lock_irq(&pps->lock);
@@ -242,6 +251,57 @@ static long pps_cdev_ioctl(struct file *file,
return 0;
}
+#ifdef CONFIG_COMPAT
+static long pps_cdev_compat_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pps_device *pps = file->private_data;
+ void __user *uarg = (void __user *) arg;
+
+ cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *));
+
+ if (cmd == PPS_FETCH) {
+ struct pps_fdata_compat compat;
+ struct pps_fdata fdata;
+ int err;
+
+ dev_dbg(pps->dev, "PPS_FETCH\n");
+
+ err = copy_from_user(&compat, uarg, sizeof(struct pps_fdata_compat));
+ if (err)
+ return -EFAULT;
+
+ memcpy(&fdata.timeout, &compat.timeout,
+ sizeof(struct pps_ktime_compat));
+
+ err = pps_cdev_pps_fetch(pps, &fdata);
+ if (err)
+ return err;
+
+ /* Return the fetched timestamp */
+ spin_lock_irq(&pps->lock);
+
+ compat.info.assert_sequence = pps->assert_sequence;
+ compat.info.clear_sequence = pps->clear_sequence;
+ compat.info.current_mode = pps->current_mode;
+
+ memcpy(&compat.info.assert_tu, &pps->assert_tu,
+ sizeof(struct pps_ktime_compat));
+ memcpy(&compat.info.clear_tu, &pps->clear_tu,
+ sizeof(struct pps_ktime_compat));
+
+ spin_unlock_irq(&pps->lock);
+
+ return copy_to_user(uarg, &compat,
+ sizeof(struct pps_fdata_compat)) ? -EFAULT : 0;
+ }
+
+ return pps_cdev_ioctl(file, cmd, arg);
+}
+#else
+#define pps_cdev_compat_ioctl NULL
+#endif
+
static int pps_cdev_open(struct inode *inode, struct file *file)
{
struct pps_device *pps = container_of(inode->i_cdev,
@@ -268,6 +328,7 @@ static const struct file_operations pps_cdev_fops = {
.llseek = no_llseek,
.poll = pps_cdev_poll,
.fasync = pps_cdev_fasync,
+ .compat_ioctl = pps_cdev_compat_ioctl,
.unlocked_ioctl = pps_cdev_ioctl,
.open = pps_cdev_open,
.release = pps_cdev_release,
diff --git a/drivers/rapidio/devices/rio_mport_cdev.c b/drivers/rapidio/devices/rio_mport_cdev.c
index 50b617af81bd..5beb0c361076 100644
--- a/drivers/rapidio/devices/rio_mport_cdev.c
+++ b/drivers/rapidio/devices/rio_mport_cdev.c
@@ -2444,31 +2444,25 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport)
mutex_init(&md->buf_mutex);
mutex_init(&md->file_mutex);
INIT_LIST_HEAD(&md->file_list);
- cdev_init(&md->cdev, &mport_fops);
- md->cdev.owner = THIS_MODULE;
- ret = cdev_add(&md->cdev, MKDEV(MAJOR(dev_number), mport->id), 1);
- if (ret < 0) {
- kfree(md);
- rmcd_error("Unable to register a device, err=%d", ret);
- return NULL;
- }
- md->dev.devt = md->cdev.dev;
+ device_initialize(&md->dev);
+ md->dev.devt = MKDEV(MAJOR(dev_number), mport->id);
md->dev.class = dev_class;
md->dev.parent = &mport->dev;
md->dev.release = mport_device_release;
dev_set_name(&md->dev, DEV_NAME "%d", mport->id);
atomic_set(&md->active, 1);
- ret = device_register(&md->dev);
+ cdev_init(&md->cdev, &mport_fops);
+ md->cdev.owner = THIS_MODULE;
+
+ ret = cdev_device_add(&md->cdev, &md->dev);
if (ret) {
rmcd_error("Failed to register mport %d (err=%d)",
mport->id, ret);
goto err_cdev;
}
- get_device(&md->dev);
-
INIT_LIST_HEAD(&md->doorbells);
spin_lock_init(&md->db_lock);
INIT_LIST_HEAD(&md->portwrites);
@@ -2513,8 +2507,7 @@ static struct mport_dev *mport_cdev_add(struct rio_mport *mport)
return md;
err_cdev:
- cdev_del(&md->cdev);
- kfree(md);
+ put_device(&md->dev);
return NULL;
}
@@ -2578,7 +2571,7 @@ static void mport_cdev_remove(struct mport_dev *md)
atomic_set(&md->active, 0);
mport_cdev_terminate_dma(md);
rio_del_mport_pw_handler(md->mport, md, rio_mport_pw_handler);
- cdev_del(&(md->cdev));
+ cdev_device_del(&md->cdev, &md->dev);
mport_cdev_kill_fasync(md);
flush_workqueue(dma_wq);
@@ -2603,7 +2596,6 @@ static void mport_cdev_remove(struct mport_dev *md)
rio_release_inb_dbell(md->mport, 0, 0x0fff);
- device_unregister(&md->dev);
put_device(&md->dev);
}
diff --git a/drivers/rapidio/rio-sysfs.c b/drivers/rapidio/rio-sysfs.c
index eda41563d06d..73e4b407f162 100644
--- a/drivers/rapidio/rio-sysfs.c
+++ b/drivers/rapidio/rio-sysfs.c
@@ -108,15 +108,11 @@ static struct attribute *rio_dev_attrs[] = {
&dev_attr_lprev.attr,
&dev_attr_destid.attr,
&dev_attr_modalias.attr,
- NULL,
-};
-static const struct attribute_group rio_dev_group = {
- .attrs = rio_dev_attrs,
-};
-
-const struct attribute_group *rio_dev_groups[] = {
- &rio_dev_group,
+ /* Switch-only attributes */
+ &dev_attr_routes.attr,
+ &dev_attr_lnext.attr,
+ &dev_attr_hopcount.attr,
NULL,
};
@@ -259,46 +255,40 @@ static struct bin_attribute rio_config_attr = {
.write = rio_write_config,
};
-/**
- * rio_create_sysfs_dev_files - create RIO specific sysfs files
- * @rdev: device whose entries should be created
- *
- * Create files when @rdev is added to sysfs.
- */
-int rio_create_sysfs_dev_files(struct rio_dev *rdev)
-{
- int err = 0;
-
- err = device_create_bin_file(&rdev->dev, &rio_config_attr);
+static struct bin_attribute *rio_dev_bin_attrs[] = {
+ &rio_config_attr,
+ NULL,
+};
- if (!err && (rdev->pef & RIO_PEF_SWITCH)) {
- err |= device_create_file(&rdev->dev, &dev_attr_routes);
- err |= device_create_file(&rdev->dev, &dev_attr_lnext);
- err |= device_create_file(&rdev->dev, &dev_attr_hopcount);
+static umode_t rio_dev_is_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct rio_dev *rdev = to_rio_dev(kobj_to_dev(kobj));
+ umode_t mode = attr->mode;
+
+ if (!(rdev->pef & RIO_PEF_SWITCH) &&
+ (attr == &dev_attr_routes.attr ||
+ attr == &dev_attr_lnext.attr ||
+ attr == &dev_attr_hopcount.attr)) {
+ /*
+ * Hide switch-specific attributes for a non-switch device.
+ */
+ mode = 0;
}
- if (err)
- pr_warning("RIO: Failed to create attribute file(s) for %s\n",
- rio_name(rdev));
-
- return err;
+ return mode;
}
-/**
- * rio_remove_sysfs_dev_files - cleanup RIO specific sysfs files
- * @rdev: device whose entries we should free
- *
- * Cleanup when @rdev is removed from sysfs.
- */
-void rio_remove_sysfs_dev_files(struct rio_dev *rdev)
-{
- device_remove_bin_file(&rdev->dev, &rio_config_attr);
- if (rdev->pef & RIO_PEF_SWITCH) {
- device_remove_file(&rdev->dev, &dev_attr_routes);
- device_remove_file(&rdev->dev, &dev_attr_lnext);
- device_remove_file(&rdev->dev, &dev_attr_hopcount);
- }
-}
+static const struct attribute_group rio_dev_group = {
+ .attrs = rio_dev_attrs,
+ .is_visible = rio_dev_is_attr_visible,
+ .bin_attrs = rio_dev_bin_attrs,
+};
+
+const struct attribute_group *rio_dev_groups[] = {
+ &rio_dev_group,
+ NULL,
+};
static ssize_t bus_scan_store(struct bus_type *bus, const char *buf,
size_t count)
diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c
index 37042858c2db..38d949405618 100644
--- a/drivers/rapidio/rio.c
+++ b/drivers/rapidio/rio.c
@@ -192,8 +192,6 @@ int rio_add_device(struct rio_dev *rdev)
}
spin_unlock(&rio_global_list_lock);
- rio_create_sysfs_dev_files(rdev);
-
return 0;
}
EXPORT_SYMBOL_GPL(rio_add_device);
@@ -220,7 +218,6 @@ void rio_del_device(struct rio_dev *rdev, enum rio_device_state state)
}
}
spin_unlock(&rio_global_list_lock);
- rio_remove_sysfs_dev_files(rdev);
device_unregister(&rdev->dev);
}
EXPORT_SYMBOL_GPL(rio_del_device);
diff --git a/drivers/rapidio/rio.h b/drivers/rapidio/rio.h
index 9796b3fee70d..b2abf8576397 100644
--- a/drivers/rapidio/rio.h
+++ b/drivers/rapidio/rio.h
@@ -27,8 +27,6 @@ extern u32 rio_mport_get_efb(struct rio_mport *port, int local, u16 destid,
u8 hopcount, u32 from);
extern int rio_mport_chk_dev_access(struct rio_mport *mport, u16 destid,
u8 hopcount);
-extern int rio_create_sysfs_dev_files(struct rio_dev *rdev);
-extern void rio_remove_sysfs_dev_files(struct rio_dev *rdev);
extern int rio_lock_device(struct rio_mport *port, u16 destid,
u8 hopcount, int wait_ms);
extern int rio_unlock_device(struct rio_mport *port, u16 destid, u8 hopcount);
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c
index 74fd9746aeca..5fb439897fe1 100644
--- a/drivers/rtc/class.c
+++ b/drivers/rtc/class.c
@@ -195,6 +195,8 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev,
goto exit_ida;
}
+ device_initialize(&rtc->dev);
+
rtc->id = id;
rtc->ops = ops;
rtc->owner = owner;
@@ -233,14 +235,19 @@ struct rtc_device *rtc_device_register(const char *name, struct device *dev,
rtc_dev_prepare(rtc);
- err = device_register(&rtc->dev);
+ err = cdev_device_add(&rtc->char_dev, &rtc->dev);
if (err) {
+ dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
+ rtc->name, MAJOR(rtc->dev.devt), rtc->id);
+
/* This will free both memory and the ID */
put_device(&rtc->dev);
goto exit;
+ } else {
+ dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,
+ MAJOR(rtc->dev.devt), rtc->id);
}
- rtc_dev_add_device(rtc);
rtc_proc_add_device(rtc);
dev_info(dev, "rtc core: registered %s as %s\n",
@@ -271,9 +278,8 @@ void rtc_device_unregister(struct rtc_device *rtc)
* Remove innards of this RTC, then disable it, before
* letting any rtc_class_open() users access it again
*/
- rtc_dev_del_device(rtc);
rtc_proc_del_device(rtc);
- device_del(&rtc->dev);
+ cdev_device_del(&rtc->char_dev, &rtc->dev);
rtc->ops = NULL;
mutex_unlock(&rtc->ops_lock);
put_device(&rtc->dev);
diff --git a/drivers/rtc/rtc-core.h b/drivers/rtc/rtc-core.h
index a098aea197fc..7a4ed2f7c7d7 100644
--- a/drivers/rtc/rtc-core.h
+++ b/drivers/rtc/rtc-core.h
@@ -3,8 +3,6 @@
extern void __init rtc_dev_init(void);
extern void __exit rtc_dev_exit(void);
extern void rtc_dev_prepare(struct rtc_device *rtc);
-extern void rtc_dev_add_device(struct rtc_device *rtc);
-extern void rtc_dev_del_device(struct rtc_device *rtc);
#else
@@ -20,14 +18,6 @@ static inline void rtc_dev_prepare(struct rtc_device *rtc)
{
}
-static inline void rtc_dev_add_device(struct rtc_device *rtc)
-{
-}
-
-static inline void rtc_dev_del_device(struct rtc_device *rtc)
-{
-}
-
#endif
#ifdef CONFIG_RTC_INTF_PROC
diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c
index 6dc8f29697ab..e81a8711fea7 100644
--- a/drivers/rtc/rtc-dev.c
+++ b/drivers/rtc/rtc-dev.c
@@ -477,23 +477,6 @@ void rtc_dev_prepare(struct rtc_device *rtc)
cdev_init(&rtc->char_dev, &rtc_dev_fops);
rtc->char_dev.owner = rtc->owner;
- rtc->char_dev.kobj.parent = &rtc->dev.kobj;
-}
-
-void rtc_dev_add_device(struct rtc_device *rtc)
-{
- if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
- dev_warn(&rtc->dev, "%s: failed to add char device %d:%d\n",
- rtc->name, MAJOR(rtc_devt), rtc->id);
- else
- dev_dbg(&rtc->dev, "%s: dev (%d:%d)\n", rtc->name,
- MAJOR(rtc_devt), rtc->id);
-}
-
-void rtc_dev_del_device(struct rtc_device *rtc)
-{
- if (rtc->dev.devt)
- cdev_del(&rtc->char_dev);
}
void __init rtc_dev_init(void)
diff --git a/drivers/scsi/osd/osd_uld.c b/drivers/scsi/osd/osd_uld.c
index ed948025112c..0e56f1eb05dc 100644
--- a/drivers/scsi/osd/osd_uld.c
+++ b/drivers/scsi/osd/osd_uld.c
@@ -400,9 +400,6 @@ static void __remove(struct device *dev)
kfree(oud->odi.osdname);
- if (oud->cdev.owner)
- cdev_del(&oud->cdev);
-
osd_dev_fini(&oud->od);
scsi_device_put(scsi_device);
@@ -411,7 +408,6 @@ static void __remove(struct device *dev)
if (oud->disk)
put_disk(oud->disk);
- ida_remove(&osd_minor_ida, oud->minor);
kfree(oud);
}
@@ -446,8 +442,21 @@ static int osd_probe(struct device *dev)
if (NULL == oud)
goto err_retract_minor;
+ /* class device member */
+ device_initialize(&oud->class_dev);
dev_set_drvdata(dev, oud);
oud->minor = minor;
+ oud->class_dev.devt = MKDEV(SCSI_OSD_MAJOR, oud->minor);
+ oud->class_dev.class = &osd_uld_class;
+ oud->class_dev.parent = dev;
+ oud->class_dev.release = __remove;
+
+ /* hold one more reference to the scsi_device that will get released
+ * in __release, in case a logout is happening while fs is mounted
+ */
+ if (scsi_device_get(scsi_device))
+ goto err_retract_minor;
+ osd_dev_init(&oud->od, scsi_device);
/* allocate a disk and set it up */
/* FIXME: do we need this since sg has already done that */
@@ -461,61 +470,34 @@ static int osd_probe(struct device *dev)
sprintf(disk->disk_name, "osd%d", oud->minor);
oud->disk = disk;
- /* hold one more reference to the scsi_device that will get released
- * in __release, in case a logout is happening while fs is mounted
- */
- if (scsi_device_get(scsi_device))
- goto err_put_disk;
- osd_dev_init(&oud->od, scsi_device);
-
/* Detect the OSD Version */
error = __detect_osd(oud);
if (error) {
OSD_ERR("osd detection failed, non-compatible OSD device\n");
- goto err_put_sdev;
+ goto err_free_osd;
}
/* init the char-device for communication with user-mode */
cdev_init(&oud->cdev, &osd_fops);
oud->cdev.owner = THIS_MODULE;
- error = cdev_add(&oud->cdev,
- MKDEV(SCSI_OSD_MAJOR, oud->minor), 1);
- if (error) {
- OSD_ERR("cdev_add failed\n");
- goto err_put_disk;
- }
- /* class device member */
- oud->class_dev.devt = oud->cdev.dev;
- oud->class_dev.class = &osd_uld_class;
- oud->class_dev.parent = dev;
- oud->class_dev.release = __remove;
error = dev_set_name(&oud->class_dev, "%s", disk->disk_name);
if (error) {
OSD_ERR("dev_set_name failed => %d\n", error);
- goto err_put_cdev;
+ goto err_free_osd;
}
- error = device_register(&oud->class_dev);
+ error = cdev_device_add(&oud->cdev, &oud->class_dev);
if (error) {
OSD_ERR("device_register failed => %d\n", error);
- goto err_put_cdev;
+ goto err_free_osd;
}
- get_device(&oud->class_dev);
-
OSD_INFO("osd_probe %s\n", disk->disk_name);
return 0;
-err_put_cdev:
- cdev_del(&oud->cdev);
-err_put_sdev:
- scsi_device_put(scsi_device);
-err_put_disk:
- put_disk(disk);
err_free_osd:
- dev_set_drvdata(dev, NULL);
- kfree(oud);
+ put_device(&oud->class_dev);
err_retract_minor:
ida_remove(&osd_minor_ida, minor);
return error;
@@ -531,9 +513,10 @@ static int osd_remove(struct device *dev)
dev, oud->od.scsi_device, scsi_device);
}
- device_unregister(&oud->class_dev);
-
+ cdev_device_del(&oud->cdev, &oud->class_dev);
+ ida_remove(&osd_minor_ida, oud->minor);
put_device(&oud->class_dev);
+
return 0;
}
diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
index 60ce7fd54e89..1c196f87e9d9 100644
--- a/drivers/uio/uio.c
+++ b/drivers/uio/uio.c
@@ -66,7 +66,7 @@ static ssize_t map_size_show(struct uio_mem *mem, char *buf)
static ssize_t map_offset_show(struct uio_mem *mem, char *buf)
{
- return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr & ~PAGE_MASK);
+ return sprintf(buf, "0x%llx\n", (unsigned long long)mem->offs);
}
struct map_sysfs_entry {
diff --git a/drivers/uio/uio_mf624.c b/drivers/uio/uio_mf624.c
index d1f95a1567bb..35187c052e8c 100644
--- a/drivers/uio/uio_mf624.c
+++ b/drivers/uio/uio_mf624.c
@@ -127,6 +127,24 @@ static int mf624_irqcontrol(struct uio_info *info, s32 irq_on)
return 0;
}
+static int mf624_setup_mem(struct pci_dev *dev, int bar, struct uio_mem *mem, const char *name)
+{
+ resource_size_t start = pci_resource_start(dev, bar);
+ resource_size_t len = pci_resource_len(dev, bar);
+
+ mem->name = name;
+ mem->addr = start & PAGE_MASK;
+ mem->offs = start & ~PAGE_MASK;
+ if (!mem->addr)
+ return -ENODEV;
+ mem->size = ((start & ~PAGE_MASK) + len + PAGE_SIZE - 1) & PAGE_MASK;
+ mem->memtype = UIO_MEM_PHYS;
+ mem->internal_addr = pci_ioremap_bar(dev, bar);
+ if (!mem->internal_addr)
+ return -ENODEV;
+ return 0;
+}
+
static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct uio_info *info;
@@ -147,37 +165,15 @@ static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
/* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */
/* BAR0 */
- info->mem[0].name = "PCI chipset, interrupts, status "
- "bits, special functions";
- info->mem[0].addr = pci_resource_start(dev, 0);
- if (!info->mem[0].addr)
+ if (mf624_setup_mem(dev, 0, &info->mem[0], "PCI chipset, interrupts, status "
+ "bits, special functions"))
goto out_release;
- info->mem[0].size = pci_resource_len(dev, 0);
- info->mem[0].memtype = UIO_MEM_PHYS;
- info->mem[0].internal_addr = pci_ioremap_bar(dev, 0);
- if (!info->mem[0].internal_addr)
- goto out_release;
-
/* BAR2 */
- info->mem[1].name = "ADC, DAC, DIO";
- info->mem[1].addr = pci_resource_start(dev, 2);
- if (!info->mem[1].addr)
- goto out_unmap0;
- info->mem[1].size = pci_resource_len(dev, 2);
- info->mem[1].memtype = UIO_MEM_PHYS;
- info->mem[1].internal_addr = pci_ioremap_bar(dev, 2);
- if (!info->mem[1].internal_addr)
+ if (mf624_setup_mem(dev, 2, &info->mem[1], "ADC, DAC, DIO"))
goto out_unmap0;
/* BAR4 */
- info->mem[2].name = "Counter/timer chip";
- info->mem[2].addr = pci_resource_start(dev, 4);
- if (!info->mem[2].addr)
- goto out_unmap1;
- info->mem[2].size = pci_resource_len(dev, 4);
- info->mem[2].memtype = UIO_MEM_PHYS;
- info->mem[2].internal_addr = pci_ioremap_bar(dev, 4);
- if (!info->mem[2].internal_addr)
+ if (mf624_setup_mem(dev, 4, &info->mem[2], "Counter/timer chip"))
goto out_unmap1;
info->irq = dev->irq;
diff --git a/drivers/vme/vme.c b/drivers/vme/vme.c
index 0035cf79760a..6a3ead42aba8 100644
--- a/drivers/vme/vme.c
+++ b/drivers/vme/vme.c
@@ -76,9 +76,16 @@ static struct vme_bridge *find_bridge(struct vme_resource *resource)
}
}
-/*
+/**
+ * vme_free_consistent - Allocate contiguous memory.
+ * @resource: Pointer to VME resource.
+ * @size: Size of allocation required.
+ * @dma: Pointer to variable to store physical address of allocation.
+ *
* Allocate a contiguous block of memory for use by the driver. This is used to
* create the buffers for the slave windows.
+ *
+ * Return: Virtual address of allocation on success, NULL on failure.
*/
void *vme_alloc_consistent(struct vme_resource *resource, size_t size,
dma_addr_t *dma)
@@ -111,8 +118,14 @@ void *vme_alloc_consistent(struct vme_resource *resource, size_t size,
}
EXPORT_SYMBOL(vme_alloc_consistent);
-/*
- * Free previously allocated contiguous block of memory.
+/**
+ * vme_free_consistent - Free previously allocated memory.
+ * @resource: Pointer to VME resource.
+ * @size: Size of allocation to free.
+ * @vaddr: Virtual address of allocation.
+ * @dma: Physical address of allocation.
+ *
+ * Free previously allocated block of contiguous memory.
*/
void vme_free_consistent(struct vme_resource *resource, size_t size,
void *vaddr, dma_addr_t dma)
@@ -145,6 +158,16 @@ void vme_free_consistent(struct vme_resource *resource, size_t size,
}
EXPORT_SYMBOL(vme_free_consistent);
+/**
+ * vme_get_size - Helper function returning size of a VME window
+ * @resource: Pointer to VME slave or master resource.
+ *
+ * Determine the size of the VME window provided. This is a helper
+ * function, wrappering the call to vme_master_get or vme_slave_get
+ * depending on the type of window resource handed to it.
+ *
+ * Return: Size of the window on success, zero on failure.
+ */
size_t vme_get_size(struct vme_resource *resource)
{
int enabled, retval;
@@ -259,9 +282,16 @@ static u32 vme_get_aspace(int am)
return 0;
}
-/*
- * Request a slave image with specific attributes, return some unique
- * identifier.
+/**
+ * vme_slave_request - Request a VME slave window resource.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @address: Required VME address space.
+ * @cycle: Required VME data transfer cycle type.
+ *
+ * Request use of a VME window resource capable of being set for the requested
+ * address space and data transfer cycle.
+ *
+ * Return: Pointer to VME resource on success, NULL on failure.
*/
struct vme_resource *vme_slave_request(struct vme_dev *vdev, u32 address,
u32 cycle)
@@ -327,6 +357,23 @@ err_bus:
}
EXPORT_SYMBOL(vme_slave_request);
+/**
+ * vme_slave_set - Set VME slave window configuration.
+ * @resource: Pointer to VME slave resource.
+ * @enabled: State to which the window should be configured.
+ * @vme_base: Base address for the window.
+ * @size: Size of the VME window.
+ * @buf_base: Based address of buffer used to provide VME slave window storage.
+ * @aspace: VME address space for the VME window.
+ * @cycle: VME data transfer cycle type for the VME window.
+ *
+ * Set configuration for provided VME slave window.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device, if an invalid resource has been provided or invalid
+ * attributes are provided. Hardware specific errors may also be
+ * returned.
+ */
int vme_slave_set(struct vme_resource *resource, int enabled,
unsigned long long vme_base, unsigned long long size,
dma_addr_t buf_base, u32 aspace, u32 cycle)
@@ -362,6 +409,21 @@ int vme_slave_set(struct vme_resource *resource, int enabled,
}
EXPORT_SYMBOL(vme_slave_set);
+/**
+ * vme_slave_get - Retrieve VME slave window configuration.
+ * @resource: Pointer to VME slave resource.
+ * @enabled: Pointer to variable for storing state.
+ * @vme_base: Pointer to variable for storing window base address.
+ * @size: Pointer to variable for storing window size.
+ * @buf_base: Pointer to variable for storing slave buffer base address.
+ * @aspace: Pointer to variable for storing VME address space.
+ * @cycle: Pointer to variable for storing VME data transfer cycle type.
+ *
+ * Return configuration for provided VME slave window.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device or if an invalid resource has been provided.
+ */
int vme_slave_get(struct vme_resource *resource, int *enabled,
unsigned long long *vme_base, unsigned long long *size,
dma_addr_t *buf_base, u32 *aspace, u32 *cycle)
@@ -386,6 +448,12 @@ int vme_slave_get(struct vme_resource *resource, int *enabled,
}
EXPORT_SYMBOL(vme_slave_get);
+/**
+ * vme_slave_free - Free VME slave window
+ * @resource: Pointer to VME slave resource.
+ *
+ * Free the provided slave resource so that it may be reallocated.
+ */
void vme_slave_free(struct vme_resource *resource)
{
struct vme_slave_resource *slave_image;
@@ -415,9 +483,17 @@ void vme_slave_free(struct vme_resource *resource)
}
EXPORT_SYMBOL(vme_slave_free);
-/*
- * Request a master image with specific attributes, return some unique
- * identifier.
+/**
+ * vme_master_request - Request a VME master window resource.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @address: Required VME address space.
+ * @cycle: Required VME data transfer cycle type.
+ * @dwidth: Required VME data transfer width.
+ *
+ * Request use of a VME window resource capable of being set for the requested
+ * address space, data transfer cycle and width.
+ *
+ * Return: Pointer to VME resource on success, NULL on failure.
*/
struct vme_resource *vme_master_request(struct vme_dev *vdev, u32 address,
u32 cycle, u32 dwidth)
@@ -486,6 +562,23 @@ err_bus:
}
EXPORT_SYMBOL(vme_master_request);
+/**
+ * vme_master_set - Set VME master window configuration.
+ * @resource: Pointer to VME master resource.
+ * @enabled: State to which the window should be configured.
+ * @vme_base: Base address for the window.
+ * @size: Size of the VME window.
+ * @aspace: VME address space for the VME window.
+ * @cycle: VME data transfer cycle type for the VME window.
+ * @dwidth: VME data transfer width for the VME window.
+ *
+ * Set configuration for provided VME master window.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device, if an invalid resource has been provided or invalid
+ * attributes are provided. Hardware specific errors may also be
+ * returned.
+ */
int vme_master_set(struct vme_resource *resource, int enabled,
unsigned long long vme_base, unsigned long long size, u32 aspace,
u32 cycle, u32 dwidth)
@@ -522,6 +615,21 @@ int vme_master_set(struct vme_resource *resource, int enabled,
}
EXPORT_SYMBOL(vme_master_set);
+/**
+ * vme_master_get - Retrieve VME master window configuration.
+ * @resource: Pointer to VME master resource.
+ * @enabled: Pointer to variable for storing state.
+ * @vme_base: Pointer to variable for storing window base address.
+ * @size: Pointer to variable for storing window size.
+ * @aspace: Pointer to variable for storing VME address space.
+ * @cycle: Pointer to variable for storing VME data transfer cycle type.
+ * @dwidth: Pointer to variable for storing VME data transfer width.
+ *
+ * Return configuration for provided VME master window.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device or if an invalid resource has been provided.
+ */
int vme_master_get(struct vme_resource *resource, int *enabled,
unsigned long long *vme_base, unsigned long long *size, u32 *aspace,
u32 *cycle, u32 *dwidth)
@@ -546,8 +654,20 @@ int vme_master_get(struct vme_resource *resource, int *enabled,
}
EXPORT_SYMBOL(vme_master_get);
-/*
- * Read data out of VME space into a buffer.
+/**
+ * vme_master_write - Read data from VME space into a buffer.
+ * @resource: Pointer to VME master resource.
+ * @buf: Pointer to buffer where data should be transferred.
+ * @count: Number of bytes to transfer.
+ * @offset: Offset into VME master window at which to start transfer.
+ *
+ * Perform read of count bytes of data from location on VME bus which maps into
+ * the VME master window at offset to buf.
+ *
+ * Return: Number of bytes read, -EINVAL if resource is not a VME master
+ * resource or read operation is not supported. -EFAULT returned if
+ * invalid offset is provided. Hardware specific errors may also be
+ * returned.
*/
ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count,
loff_t offset)
@@ -583,8 +703,20 @@ ssize_t vme_master_read(struct vme_resource *resource, void *buf, size_t count,
}
EXPORT_SYMBOL(vme_master_read);
-/*
- * Write data out to VME space from a buffer.
+/**
+ * vme_master_write - Write data out to VME space from a buffer.
+ * @resource: Pointer to VME master resource.
+ * @buf: Pointer to buffer holding data to transfer.
+ * @count: Number of bytes to transfer.
+ * @offset: Offset into VME master window at which to start transfer.
+ *
+ * Perform write of count bytes of data from buf to location on VME bus which
+ * maps into the VME master window at offset.
+ *
+ * Return: Number of bytes written, -EINVAL if resource is not a VME master
+ * resource or write operation is not supported. -EFAULT returned if
+ * invalid offset is provided. Hardware specific errors may also be
+ * returned.
*/
ssize_t vme_master_write(struct vme_resource *resource, void *buf,
size_t count, loff_t offset)
@@ -619,8 +751,24 @@ ssize_t vme_master_write(struct vme_resource *resource, void *buf,
}
EXPORT_SYMBOL(vme_master_write);
-/*
- * Perform RMW cycle to provided location.
+/**
+ * vme_master_rmw - Perform read-modify-write cycle.
+ * @resource: Pointer to VME master resource.
+ * @mask: Bits to be compared and swapped in operation.
+ * @compare: Bits to be compared with data read from offset.
+ * @swap: Bits to be swapped in data read from offset.
+ * @offset: Offset into VME master window at which to perform operation.
+ *
+ * Perform read-modify-write cycle on provided location:
+ * - Location on VME bus is read.
+ * - Bits selected by mask are compared with compare.
+ * - Where a selected bit matches that in compare and are selected in swap,
+ * the bit is swapped.
+ * - Result written back to location on VME bus.
+ *
+ * Return: Bytes written on success, -EINVAL if resource is not a VME master
+ * resource or RMW operation is not supported. Hardware specific
+ * errors may also be returned.
*/
unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask,
unsigned int compare, unsigned int swap, loff_t offset)
@@ -644,6 +792,17 @@ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask,
}
EXPORT_SYMBOL(vme_master_rmw);
+/**
+ * vme_master_mmap - Mmap region of VME master window.
+ * @resource: Pointer to VME master resource.
+ * @vma: Pointer to definition of user mapping.
+ *
+ * Memory map a region of the VME master window into user space.
+ *
+ * Return: Zero on success, -EINVAL if resource is not a VME master
+ * resource or -EFAULT if map exceeds window size. Other generic mmap
+ * errors may also be returned.
+ */
int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma)
{
struct vme_master_resource *image;
@@ -670,6 +829,12 @@ int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma)
}
EXPORT_SYMBOL(vme_master_mmap);
+/**
+ * vme_master_free - Free VME master window
+ * @resource: Pointer to VME master resource.
+ *
+ * Free the provided master resource so that it may be reallocated.
+ */
void vme_master_free(struct vme_resource *resource)
{
struct vme_master_resource *master_image;
@@ -699,9 +864,15 @@ void vme_master_free(struct vme_resource *resource)
}
EXPORT_SYMBOL(vme_master_free);
-/*
- * Request a DMA controller with specific attributes, return some unique
- * identifier.
+/**
+ * vme_dma_request - Request a DMA controller.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @route: Required src/destination combination.
+ *
+ * Request a VME DMA controller with capability to perform transfers bewteen
+ * requested source/destination combination.
+ *
+ * Return: Pointer to VME DMA resource on success, NULL on failure.
*/
struct vme_resource *vme_dma_request(struct vme_dev *vdev, u32 route)
{
@@ -768,8 +939,15 @@ err_bus:
}
EXPORT_SYMBOL(vme_dma_request);
-/*
- * Start new list
+/**
+ * vme_new_dma_list - Create new VME DMA list.
+ * @resource: Pointer to VME DMA resource.
+ *
+ * Create a new VME DMA list. It is the responsibility of the user to free
+ * the list once it is no longer required with vme_dma_list_free().
+ *
+ * Return: Pointer to new VME DMA list, NULL on allocation failure or invalid
+ * VME DMA resource.
*/
struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource)
{
@@ -796,8 +974,16 @@ struct vme_dma_list *vme_new_dma_list(struct vme_resource *resource)
}
EXPORT_SYMBOL(vme_new_dma_list);
-/*
- * Create "Pattern" type attributes
+/**
+ * vme_dma_pattern_attribute - Create "Pattern" type VME DMA list attribute.
+ * @pattern: Value to use used as pattern
+ * @type: Type of pattern to be written.
+ *
+ * Create VME DMA list attribute for pattern generation. It is the
+ * responsibility of the user to free used attributes using
+ * vme_dma_free_attribute().
+ *
+ * Return: Pointer to VME DMA attribute, NULL on failure.
*/
struct vme_dma_attr *vme_dma_pattern_attribute(u32 pattern, u32 type)
{
@@ -831,8 +1017,15 @@ err_attr:
}
EXPORT_SYMBOL(vme_dma_pattern_attribute);
-/*
- * Create "PCI" type attributes
+/**
+ * vme_dma_pci_attribute - Create "PCI" type VME DMA list attribute.
+ * @address: PCI base address for DMA transfer.
+ *
+ * Create VME DMA list attribute pointing to a location on PCI for DMA
+ * transfers. It is the responsibility of the user to free used attributes
+ * using vme_dma_free_attribute().
+ *
+ * Return: Pointer to VME DMA attribute, NULL on failure.
*/
struct vme_dma_attr *vme_dma_pci_attribute(dma_addr_t address)
{
@@ -869,8 +1062,18 @@ err_attr:
}
EXPORT_SYMBOL(vme_dma_pci_attribute);
-/*
- * Create "VME" type attributes
+/**
+ * vme_dma_vme_attribute - Create "VME" type VME DMA list attribute.
+ * @address: VME base address for DMA transfer.
+ * @aspace: VME address space to use for DMA transfer.
+ * @cycle: VME bus cycle to use for DMA transfer.
+ * @dwidth: VME data width to use for DMA transfer.
+ *
+ * Create VME DMA list attribute pointing to a location on the VME bus for DMA
+ * transfers. It is the responsibility of the user to free used attributes
+ * using vme_dma_free_attribute().
+ *
+ * Return: Pointer to VME DMA attribute, NULL on failure.
*/
struct vme_dma_attr *vme_dma_vme_attribute(unsigned long long address,
u32 aspace, u32 cycle, u32 dwidth)
@@ -908,8 +1111,12 @@ err_attr:
}
EXPORT_SYMBOL(vme_dma_vme_attribute);
-/*
- * Free attribute
+/**
+ * vme_dma_free_attribute - Free DMA list attribute.
+ * @attributes: Pointer to DMA list attribute.
+ *
+ * Free VME DMA list attribute. VME DMA list attributes can be safely freed
+ * once vme_dma_list_add() has returned.
*/
void vme_dma_free_attribute(struct vme_dma_attr *attributes)
{
@@ -918,6 +1125,23 @@ void vme_dma_free_attribute(struct vme_dma_attr *attributes)
}
EXPORT_SYMBOL(vme_dma_free_attribute);
+/**
+ * vme_dma_list_add - Add enty to a VME DMA list.
+ * @list: Pointer to VME list.
+ * @src: Pointer to DMA list attribute to use as source.
+ * @dest: Pointer to DMA list attribute to use as destination.
+ * @count: Number of bytes to transfer.
+ *
+ * Add an entry to the provided VME DMA list. Entry requires pointers to source
+ * and destination DMA attributes and a count.
+ *
+ * Please note, the attributes supported as source and destinations for
+ * transfers are hardware dependent.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device or if the link list has already been submitted for execution.
+ * Hardware specific errors also possible.
+ */
int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src,
struct vme_dma_attr *dest, size_t count)
{
@@ -942,6 +1166,16 @@ int vme_dma_list_add(struct vme_dma_list *list, struct vme_dma_attr *src,
}
EXPORT_SYMBOL(vme_dma_list_add);
+/**
+ * vme_dma_list_exec - Queue a VME DMA list for execution.
+ * @list: Pointer to VME list.
+ *
+ * Queue the provided VME DMA list for execution. The call will return once the
+ * list has been executed.
+ *
+ * Return: Zero on success, -EINVAL if operation is not supported on this
+ * device. Hardware specific errors also possible.
+ */
int vme_dma_list_exec(struct vme_dma_list *list)
{
struct vme_bridge *bridge = list->parent->parent;
@@ -962,6 +1196,15 @@ int vme_dma_list_exec(struct vme_dma_list *list)
}
EXPORT_SYMBOL(vme_dma_list_exec);
+/**
+ * vme_dma_list_free - Free a VME DMA list.
+ * @list: Pointer to VME list.
+ *
+ * Free the provided DMA list and all its entries.
+ *
+ * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource
+ * is still in use. Hardware specific errors also possible.
+ */
int vme_dma_list_free(struct vme_dma_list *list)
{
struct vme_bridge *bridge = list->parent->parent;
@@ -994,6 +1237,15 @@ int vme_dma_list_free(struct vme_dma_list *list)
}
EXPORT_SYMBOL(vme_dma_list_free);
+/**
+ * vme_dma_free - Free a VME DMA resource.
+ * @resource: Pointer to VME DMA resource.
+ *
+ * Free the provided DMA resource so that it may be reallocated.
+ *
+ * Return: Zero on success, -EINVAL on invalid VME resource, -EBUSY if resource
+ * is still active.
+ */
int vme_dma_free(struct vme_resource *resource)
{
struct vme_dma_resource *ctrlr;
@@ -1099,6 +1351,22 @@ void vme_irq_handler(struct vme_bridge *bridge, int level, int statid)
}
EXPORT_SYMBOL(vme_irq_handler);
+/**
+ * vme_irq_request - Request a specific VME interrupt.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @level: Interrupt priority being requested.
+ * @statid: Interrupt vector being requested.
+ * @callback: Pointer to callback function called when VME interrupt/vector
+ * received.
+ * @priv_data: Generic pointer that will be passed to the callback function.
+ *
+ * Request callback to be attached as a handler for VME interrupts with provided
+ * level and statid.
+ *
+ * Return: Zero on success, -EINVAL on invalid vme device, level or if the
+ * function is not supported, -EBUSY if the level/statid combination is
+ * already in use. Hardware specific errors also possible.
+ */
int vme_irq_request(struct vme_dev *vdev, int level, int statid,
void (*callback)(int, int, void *),
void *priv_data)
@@ -1142,6 +1410,14 @@ int vme_irq_request(struct vme_dev *vdev, int level, int statid,
}
EXPORT_SYMBOL(vme_irq_request);
+/**
+ * vme_irq_free - Free a VME interrupt.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @level: Interrupt priority of interrupt being freed.
+ * @statid: Interrupt vector of interrupt being freed.
+ *
+ * Remove previously attached callback from VME interrupt priority/vector.
+ */
void vme_irq_free(struct vme_dev *vdev, int level, int statid)
{
struct vme_bridge *bridge;
@@ -1177,6 +1453,18 @@ void vme_irq_free(struct vme_dev *vdev, int level, int statid)
}
EXPORT_SYMBOL(vme_irq_free);
+/**
+ * vme_irq_generate - Generate VME interrupt.
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ * @level: Interrupt priority at which to assert the interrupt.
+ * @statid: Interrupt vector to associate with the interrupt.
+ *
+ * Generate a VME interrupt of the provided level and with the provided
+ * statid.
+ *
+ * Return: Zero on success, -EINVAL on invalid vme device, level or if the
+ * function is not supported. Hardware specific errors also possible.
+ */
int vme_irq_generate(struct vme_dev *vdev, int level, int statid)
{
struct vme_bridge *bridge;
@@ -1201,8 +1489,15 @@ int vme_irq_generate(struct vme_dev *vdev, int level, int statid)
}
EXPORT_SYMBOL(vme_irq_generate);
-/*
- * Request the location monitor, return resource or NULL
+/**
+ * vme_lm_request - Request a VME location monitor
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ *
+ * Allocate a location monitor resource to the driver. A location monitor
+ * allows the driver to monitor accesses to a contiguous number of
+ * addresses on the VME bus.
+ *
+ * Return: Pointer to a VME resource on success or NULL on failure.
*/
struct vme_resource *vme_lm_request(struct vme_dev *vdev)
{
@@ -1218,7 +1513,7 @@ struct vme_resource *vme_lm_request(struct vme_dev *vdev)
goto err_bus;
}
- /* Loop through DMA resources */
+ /* Loop through LM resources */
list_for_each(lm_pos, &bridge->lm_resources) {
lm = list_entry(lm_pos,
struct vme_lm_resource, list);
@@ -1264,6 +1559,17 @@ err_bus:
}
EXPORT_SYMBOL(vme_lm_request);
+/**
+ * vme_lm_count - Determine number of VME Addresses monitored
+ * @resource: Pointer to VME location monitor resource.
+ *
+ * The number of contiguous addresses monitored is hardware dependent.
+ * Return the number of contiguous addresses monitored by the
+ * location monitor.
+ *
+ * Return: Count of addresses monitored or -EINVAL when provided with an
+ * invalid location monitor resource.
+ */
int vme_lm_count(struct vme_resource *resource)
{
struct vme_lm_resource *lm;
@@ -1279,6 +1585,20 @@ int vme_lm_count(struct vme_resource *resource)
}
EXPORT_SYMBOL(vme_lm_count);
+/**
+ * vme_lm_set - Configure location monitor
+ * @resource: Pointer to VME location monitor resource.
+ * @lm_base: Base address to monitor.
+ * @aspace: VME address space to monitor.
+ * @cycle: VME bus cycle type to monitor.
+ *
+ * Set the base address, address space and cycle type of accesses to be
+ * monitored by the location monitor.
+ *
+ * Return: Zero on success, -EINVAL when provided with an invalid location
+ * monitor resource or function is not supported. Hardware specific
+ * errors may also be returned.
+ */
int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base,
u32 aspace, u32 cycle)
{
@@ -1301,6 +1621,20 @@ int vme_lm_set(struct vme_resource *resource, unsigned long long lm_base,
}
EXPORT_SYMBOL(vme_lm_set);
+/**
+ * vme_lm_get - Retrieve location monitor settings
+ * @resource: Pointer to VME location monitor resource.
+ * @lm_base: Pointer used to output the base address monitored.
+ * @aspace: Pointer used to output the address space monitored.
+ * @cycle: Pointer used to output the VME bus cycle type monitored.
+ *
+ * Retrieve the base address, address space and cycle type of accesses to
+ * be monitored by the location monitor.
+ *
+ * Return: Zero on success, -EINVAL when provided with an invalid location
+ * monitor resource or function is not supported. Hardware specific
+ * errors may also be returned.
+ */
int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base,
u32 *aspace, u32 *cycle)
{
@@ -1323,6 +1657,21 @@ int vme_lm_get(struct vme_resource *resource, unsigned long long *lm_base,
}
EXPORT_SYMBOL(vme_lm_get);
+/**
+ * vme_lm_attach - Provide callback for location monitor address
+ * @resource: Pointer to VME location monitor resource.
+ * @monitor: Offset to which callback should be attached.
+ * @callback: Pointer to callback function called when triggered.
+ * @data: Generic pointer that will be passed to the callback function.
+ *
+ * Attach a callback to the specificed offset into the location monitors
+ * monitored addresses. A generic pointer is provided to allow data to be
+ * passed to the callback when called.
+ *
+ * Return: Zero on success, -EINVAL when provided with an invalid location
+ * monitor resource or function is not supported. Hardware specific
+ * errors may also be returned.
+ */
int vme_lm_attach(struct vme_resource *resource, int monitor,
void (*callback)(void *), void *data)
{
@@ -1345,6 +1694,18 @@ int vme_lm_attach(struct vme_resource *resource, int monitor,
}
EXPORT_SYMBOL(vme_lm_attach);
+/**
+ * vme_lm_detach - Remove callback for location monitor address
+ * @resource: Pointer to VME location monitor resource.
+ * @monitor: Offset to which callback should be removed.
+ *
+ * Remove the callback associated with the specificed offset into the
+ * location monitors monitored addresses.
+ *
+ * Return: Zero on success, -EINVAL when provided with an invalid location
+ * monitor resource or function is not supported. Hardware specific
+ * errors may also be returned.
+ */
int vme_lm_detach(struct vme_resource *resource, int monitor)
{
struct vme_bridge *bridge = find_bridge(resource);
@@ -1366,6 +1727,18 @@ int vme_lm_detach(struct vme_resource *resource, int monitor)
}
EXPORT_SYMBOL(vme_lm_detach);
+/**
+ * vme_lm_free - Free allocated VME location monitor
+ * @resource: Pointer to VME location monitor resource.
+ *
+ * Free allocation of a VME location monitor.
+ *
+ * WARNING: This function currently expects that any callbacks that have
+ * been attached to the location monitor have been removed.
+ *
+ * Return: Zero on success, -EINVAL when provided with an invalid location
+ * monitor resource.
+ */
void vme_lm_free(struct vme_resource *resource)
{
struct vme_lm_resource *lm;
@@ -1392,6 +1765,16 @@ void vme_lm_free(struct vme_resource *resource)
}
EXPORT_SYMBOL(vme_lm_free);
+/**
+ * vme_slot_num - Retrieve slot ID
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ *
+ * Retrieve the slot ID associated with the provided VME device.
+ *
+ * Return: The slot ID on success, -EINVAL if VME bridge cannot be determined
+ * or the function is not supported. Hardware specific errors may also
+ * be returned.
+ */
int vme_slot_num(struct vme_dev *vdev)
{
struct vme_bridge *bridge;
@@ -1411,6 +1794,15 @@ int vme_slot_num(struct vme_dev *vdev)
}
EXPORT_SYMBOL(vme_slot_num);
+/**
+ * vme_bus_num - Retrieve bus number
+ * @vdev: Pointer to VME device struct vme_dev assigned to driver instance.
+ *
+ * Retrieve the bus enumeration associated with the provided VME device.
+ *
+ * Return: The bus number on success, -EINVAL if VME bridge cannot be
+ * determined.
+ */
int vme_bus_num(struct vme_dev *vdev)
{
struct vme_bridge *bridge;
@@ -1556,6 +1948,15 @@ static int __vme_register_driver(struct vme_driver *drv, unsigned int ndevs)
return err;
}
+/**
+ * vme_register_driver - Register a VME driver
+ * @drv: Pointer to VME driver structure to register.
+ * @ndevs: Maximum number of devices to allow to be enumerated.
+ *
+ * Register a VME device driver with the VME subsystem.
+ *
+ * Return: Zero on success, error value on registration failure.
+ */
int vme_register_driver(struct vme_driver *drv, unsigned int ndevs)
{
int err;
@@ -1576,6 +1977,12 @@ int vme_register_driver(struct vme_driver *drv, unsigned int ndevs)
}
EXPORT_SYMBOL(vme_register_driver);
+/**
+ * vme_unregister_driver - Unregister a VME driver
+ * @drv: Pointer to VME driver structure to unregister.
+ *
+ * Unregister a VME device driver from the VME subsystem.
+ */
void vme_unregister_driver(struct vme_driver *drv)
{
struct vme_dev *dev, *dev_tmp;
diff --git a/drivers/w1/masters/matrox_w1.c b/drivers/w1/masters/matrox_w1.c
index 3749db8b4396..97a676bf5989 100644
--- a/drivers/w1/masters/matrox_w1.c
+++ b/drivers/w1/masters/matrox_w1.c
@@ -36,7 +36,6 @@
#include "../w1.h"
#include "../w1_int.h"
-#include "../w1_log.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
@@ -157,9 +156,6 @@ static int matrox_w1_probe(struct pci_dev *pdev, const struct pci_device_id *ent
struct matrox_device *dev;
int err;
- assert(pdev != NULL);
- assert(ent != NULL);
-
if (pdev->vendor != PCI_VENDOR_ID_MATROX || pdev->device != PCI_DEVICE_ID_MATROX_G400)
return -ENODEV;
@@ -224,8 +220,6 @@ static void matrox_w1_remove(struct pci_dev *pdev)
{
struct matrox_device *dev = pci_get_drvdata(pdev);
- assert(dev != NULL);
-
if (dev->found) {
w1_remove_master_device(dev->bus_master);
iounmap(dev->virt_addr);
diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig
index 0ef9f2663dbd..fb68465908f2 100644
--- a/drivers/w1/slaves/Kconfig
+++ b/drivers/w1/slaves/Kconfig
@@ -86,6 +86,12 @@ config W1_SLAVE_DS2433_CRC
Each block has 30 bytes of data and a two byte CRC16.
Full block writes are only allowed if the CRC is valid.
+config W1_SLAVE_DS2438
+ tristate "DS2438 Smart Battery Monitor 0x26 family support"
+ help
+ Say Y here if you want to use a 1-wire
+ DS2438 Smart Battery Monitor device support
+
config W1_SLAVE_DS2760
tristate "Dallas 2760 battery monitor chip (HP iPAQ & others)"
help
diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile
index b4a358955ef9..54c63e420302 100644
--- a/drivers/w1/slaves/Makefile
+++ b/drivers/w1/slaves/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o
obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o
obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o
obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o
+obj-$(CONFIG_W1_SLAVE_DS2438) += w1_ds2438.o
obj-$(CONFIG_W1_SLAVE_DS2760) += w1_ds2760.o
obj-$(CONFIG_W1_SLAVE_DS2780) += w1_ds2780.o
obj-$(CONFIG_W1_SLAVE_DS2781) += w1_ds2781.o
diff --git a/drivers/w1/slaves/w1_ds2438.c b/drivers/w1/slaves/w1_ds2438.c
new file mode 100644
index 000000000000..5ededb4965e1
--- /dev/null
+++ b/drivers/w1/slaves/w1_ds2438.c
@@ -0,0 +1,390 @@
+/*
+ * 1-Wire implementation for the ds2438 chip
+ *
+ * Copyright (c) 2017 Mariusz Bialonczyk <manio@skyboo.net>
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2. See the file COPYING for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+
+#include "../w1.h"
+#include "../w1_family.h"
+
+#define W1_DS2438_RETRIES 3
+
+/* Memory commands */
+#define W1_DS2438_READ_SCRATCH 0xBE
+#define W1_DS2438_WRITE_SCRATCH 0x4E
+#define W1_DS2438_COPY_SCRATCH 0x48
+#define W1_DS2438_RECALL_MEMORY 0xB8
+/* Register commands */
+#define W1_DS2438_CONVERT_TEMP 0x44
+#define W1_DS2438_CONVERT_VOLTAGE 0xB4
+
+#define DS2438_PAGE_SIZE 8
+#define DS2438_ADC_INPUT_VAD 0
+#define DS2438_ADC_INPUT_VDD 1
+#define DS2438_MAX_CONVERSION_TIME 10 /* ms */
+
+/* Page #0 definitions */
+#define DS2438_STATUS_REG 0x00 /* Status/Configuration Register */
+#define DS2438_STATUS_IAD (1 << 0) /* Current A/D Control Bit */
+#define DS2438_STATUS_CA (1 << 1) /* Current Accumulator Configuration */
+#define DS2438_STATUS_EE (1 << 2) /* Current Accumulator Shadow Selector bit */
+#define DS2438_STATUS_AD (1 << 3) /* Voltage A/D Input Select Bit */
+#define DS2438_STATUS_TB (1 << 4) /* Temperature Busy Flag */
+#define DS2438_STATUS_NVB (1 << 5) /* Nonvolatile Memory Busy Flag */
+#define DS2438_STATUS_ADB (1 << 6) /* A/D Converter Busy Flag */
+
+#define DS2438_TEMP_LSB 0x01
+#define DS2438_TEMP_MSB 0x02
+#define DS2438_VOLTAGE_LSB 0x03
+#define DS2438_VOLTAGE_MSB 0x04
+#define DS2438_CURRENT_LSB 0x05
+#define DS2438_CURRENT_MSB 0x06
+#define DS2438_THRESHOLD 0x07
+
+int w1_ds2438_get_page(struct w1_slave *sl, int pageno, u8 *buf)
+{
+ unsigned int retries = W1_DS2438_RETRIES;
+ u8 w1_buf[2];
+ u8 crc;
+ size_t count;
+
+ while (retries--) {
+ crc = 0;
+
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_RECALL_MEMORY;
+ w1_buf[1] = 0x00;
+ w1_write_block(sl->master, w1_buf, 2);
+
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_READ_SCRATCH;
+ w1_buf[1] = 0x00;
+ w1_write_block(sl->master, w1_buf, 2);
+
+ count = w1_read_block(sl->master, buf, DS2438_PAGE_SIZE + 1);
+ if (count == DS2438_PAGE_SIZE + 1) {
+ crc = w1_calc_crc8(buf, DS2438_PAGE_SIZE);
+
+ /* check for correct CRC */
+ if ((u8)buf[DS2438_PAGE_SIZE] == crc)
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int w1_ds2438_get_temperature(struct w1_slave *sl, int16_t *temperature)
+{
+ unsigned int retries = W1_DS2438_RETRIES;
+ u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];
+ unsigned int tm = DS2438_MAX_CONVERSION_TIME;
+ unsigned long sleep_rem;
+ int ret;
+
+ mutex_lock(&sl->master->bus_mutex);
+
+ while (retries--) {
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_write_8(sl->master, W1_DS2438_CONVERT_TEMP);
+
+ mutex_unlock(&sl->master->bus_mutex);
+ sleep_rem = msleep_interruptible(tm);
+ if (sleep_rem != 0) {
+ ret = -1;
+ goto post_unlock;
+ }
+
+ if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) {
+ ret = -1;
+ goto post_unlock;
+ }
+
+ break;
+ }
+
+ if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {
+ *temperature = (((int16_t) w1_buf[DS2438_TEMP_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_TEMP_LSB]);
+ ret = 0;
+ } else
+ ret = -1;
+
+ mutex_unlock(&sl->master->bus_mutex);
+
+post_unlock:
+ return ret;
+}
+
+int w1_ds2438_change_config_bit(struct w1_slave *sl, u8 mask, u8 value)
+{
+ unsigned int retries = W1_DS2438_RETRIES;
+ u8 w1_buf[3];
+ u8 status;
+ int perform_write = 0;
+
+ while (retries--) {
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_RECALL_MEMORY;
+ w1_buf[1] = 0x00;
+ w1_write_block(sl->master, w1_buf, 2);
+
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_READ_SCRATCH;
+ w1_buf[1] = 0x00;
+ w1_write_block(sl->master, w1_buf, 2);
+
+ /* reading one byte of result */
+ status = w1_read_8(sl->master);
+
+ /* if bit0=1, set a value to a mask for easy compare */
+ if (value)
+ value = mask;
+
+ if ((status & mask) == value)
+ return 0; /* already set as requested */
+ else {
+ /* changing bit */
+ status ^= mask;
+ perform_write = 1;
+ }
+ break;
+ }
+
+ if (perform_write) {
+ retries = W1_DS2438_RETRIES;
+ while (retries--) {
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_WRITE_SCRATCH;
+ w1_buf[1] = 0x00;
+ w1_buf[2] = status;
+ w1_write_block(sl->master, w1_buf, 3);
+
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_buf[0] = W1_DS2438_COPY_SCRATCH;
+ w1_buf[1] = 0x00;
+ w1_write_block(sl->master, w1_buf, 2);
+
+ return 0;
+ }
+ }
+ return -1;
+}
+
+uint16_t w1_ds2438_get_voltage(struct w1_slave *sl, int adc_input, uint16_t *voltage)
+{
+ unsigned int retries = W1_DS2438_RETRIES;
+ u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];
+ unsigned int tm = DS2438_MAX_CONVERSION_TIME;
+ unsigned long sleep_rem;
+ int ret;
+
+ mutex_lock(&sl->master->bus_mutex);
+
+ if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_AD, adc_input)) {
+ ret = -1;
+ goto pre_unlock;
+ }
+
+ while (retries--) {
+ if (w1_reset_select_slave(sl))
+ continue;
+ w1_write_8(sl->master, W1_DS2438_CONVERT_VOLTAGE);
+
+ mutex_unlock(&sl->master->bus_mutex);
+ sleep_rem = msleep_interruptible(tm);
+ if (sleep_rem != 0) {
+ ret = -1;
+ goto post_unlock;
+ }
+
+ if (mutex_lock_interruptible(&sl->master->bus_mutex) != 0) {
+ ret = -1;
+ goto post_unlock;
+ }
+
+ break;
+ }
+
+ if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {
+ *voltage = (((uint16_t) w1_buf[DS2438_VOLTAGE_MSB]) << 8) | ((uint16_t) w1_buf[DS2438_VOLTAGE_LSB]);
+ ret = 0;
+ } else
+ ret = -1;
+
+pre_unlock:
+ mutex_unlock(&sl->master->bus_mutex);
+
+post_unlock:
+ return ret;
+}
+
+static ssize_t iad_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct w1_slave *sl = kobj_to_w1_slave(kobj);
+ int ret;
+
+ if (count != 1 || off != 0)
+ return -EFAULT;
+
+ mutex_lock(&sl->master->bus_mutex);
+
+ if (w1_ds2438_change_config_bit(sl, DS2438_STATUS_IAD, *buf & 0x01) == 0)
+ ret = 1;
+ else
+ ret = -EIO;
+
+ mutex_unlock(&sl->master->bus_mutex);
+
+ return ret;
+}
+
+static ssize_t page0_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct w1_slave *sl = kobj_to_w1_slave(kobj);
+ int ret;
+ u8 w1_buf[DS2438_PAGE_SIZE + 1 /*for CRC*/];
+
+ if (off != 0)
+ return 0;
+ if (!buf)
+ return -EINVAL;
+
+ mutex_lock(&sl->master->bus_mutex);
+
+ if (w1_ds2438_get_page(sl, 0, w1_buf) == 0) {
+ memcpy(buf, &w1_buf, DS2438_PAGE_SIZE);
+ ret = DS2438_PAGE_SIZE;
+ } else
+ ret = -EIO;
+
+ mutex_unlock(&sl->master->bus_mutex);
+
+ return ret;
+}
+
+static ssize_t temperature_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct w1_slave *sl = kobj_to_w1_slave(kobj);
+ int ret;
+ ssize_t c = PAGE_SIZE;
+ int16_t temp;
+
+ if (off != 0)
+ return 0;
+ if (!buf)
+ return -EINVAL;
+
+ if (w1_ds2438_get_temperature(sl, &temp) == 0) {
+ c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", temp);
+ ret = PAGE_SIZE - c;
+ } else
+ ret = -EIO;
+
+ return ret;
+}
+
+static ssize_t vad_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct w1_slave *sl = kobj_to_w1_slave(kobj);
+ int ret;
+ ssize_t c = PAGE_SIZE;
+ uint16_t voltage;
+
+ if (off != 0)
+ return 0;
+ if (!buf)
+ return -EINVAL;
+
+ if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VAD, &voltage) == 0) {
+ c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage);
+ ret = PAGE_SIZE - c;
+ } else
+ ret = -EIO;
+
+ return ret;
+}
+
+static ssize_t vdd_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct w1_slave *sl = kobj_to_w1_slave(kobj);
+ int ret;
+ ssize_t c = PAGE_SIZE;
+ uint16_t voltage;
+
+ if (off != 0)
+ return 0;
+ if (!buf)
+ return -EINVAL;
+
+ if (w1_ds2438_get_voltage(sl, DS2438_ADC_INPUT_VDD, &voltage) == 0) {
+ c -= snprintf(buf + PAGE_SIZE - c, c, "%d\n", voltage);
+ ret = PAGE_SIZE - c;
+ } else
+ ret = -EIO;
+
+ return ret;
+}
+
+static BIN_ATTR(iad, S_IRUGO | S_IWUSR | S_IWGRP, NULL, iad_write, 1);
+static BIN_ATTR_RO(page0, DS2438_PAGE_SIZE);
+static BIN_ATTR_RO(temperature, 0/* real length varies */);
+static BIN_ATTR_RO(vad, 0/* real length varies */);
+static BIN_ATTR_RO(vdd, 0/* real length varies */);
+
+static struct bin_attribute *w1_ds2438_bin_attrs[] = {
+ &bin_attr_iad,
+ &bin_attr_page0,
+ &bin_attr_temperature,
+ &bin_attr_vad,
+ &bin_attr_vdd,
+ NULL,
+};
+
+static const struct attribute_group w1_ds2438_group = {
+ .bin_attrs = w1_ds2438_bin_attrs,
+};
+
+static const struct attribute_group *w1_ds2438_groups[] = {
+ &w1_ds2438_group,
+ NULL,
+};
+
+static struct w1_family_ops w1_ds2438_fops = {
+ .groups = w1_ds2438_groups,
+};
+
+static struct w1_family w1_ds2438_family = {
+ .fid = W1_FAMILY_DS2438,
+ .fops = &w1_ds2438_fops,
+};
+module_w1_family(w1_ds2438_family);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mariusz Bialonczyk <manio@skyboo.net>");
+MODULE_DESCRIPTION("1-wire driver for Maxim/Dallas DS2438 Smart Battery Monitor");
+MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2438));
diff --git a/drivers/w1/slaves/w1_ds2760.h b/drivers/w1/slaves/w1_ds2760.h
index 58e774141568..24168c94eeae 100644
--- a/drivers/w1/slaves/w1_ds2760.h
+++ b/drivers/w1/slaves/w1_ds2760.h
@@ -24,11 +24,13 @@
#define DS2760_DATA_SIZE 0x40
#define DS2760_PROTECTION_REG 0x00
+
#define DS2760_STATUS_REG 0x01
- #define DS2760_STATUS_IE (1 << 2)
- #define DS2760_STATUS_SWEN (1 << 3)
- #define DS2760_STATUS_RNAOP (1 << 4)
- #define DS2760_STATUS_PMOD (1 << 5)
+#define DS2760_STATUS_IE (1 << 2)
+#define DS2760_STATUS_SWEN (1 << 3)
+#define DS2760_STATUS_RNAOP (1 << 4)
+#define DS2760_STATUS_PMOD (1 << 5)
+
#define DS2760_EEPROM_REG 0x07
#define DS2760_SPECIAL_FEATURE_REG 0x08
#define DS2760_VOLTAGE_MSB 0x0c
diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c
index 90a3d9338fd2..8511d1685db9 100644
--- a/drivers/w1/w1.c
+++ b/drivers/w1/w1.c
@@ -29,7 +29,6 @@
#include <linux/atomic.h>
#include "w1.h"
-#include "w1_log.h"
#include "w1_int.h"
#include "w1_family.h"
#include "w1_netlink.h"
diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h
index c4a6b257a367..869a3ff87d29 100644
--- a/drivers/w1/w1_family.h
+++ b/drivers/w1/w1_family.h
@@ -29,6 +29,7 @@
#define W1_COUNTER_DS2423 0x1D
#define W1_THERM_DS1822 0x22
#define W1_EEPROM_DS2433 0x23
+#define W1_FAMILY_DS2438 0x26
#define W1_THERM_DS18B20 0x28
#define W1_FAMILY_DS2408 0x29
#define W1_EEPROM_DS2431 0x2D
diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c
index 2cae7b29bb5f..1072a2e620bb 100644
--- a/drivers/w1/w1_int.c
+++ b/drivers/w1/w1_int.c
@@ -22,7 +22,6 @@
#include <linux/moduleparam.h>
#include "w1.h"
-#include "w1_log.h"
#include "w1_netlink.h"
#include "w1_int.h"
diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c
index de8bebc27896..1134e6b1eb02 100644
--- a/drivers/w1/w1_io.c
+++ b/drivers/w1/w1_io.c
@@ -19,7 +19,6 @@
#include <linux/module.h>
#include "w1.h"
-#include "w1_log.h"
static int w1_delay_parm = 1;
module_param_named(delay_coef, w1_delay_parm, int, 0);
diff --git a/drivers/w1/w1_log.h b/drivers/w1/w1_log.h
deleted file mode 100644
index dd1422b6afbb..000000000000
--- a/drivers/w1/w1_log.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2004 Evgeniy Polyakov <zbr@ioremap.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
-#ifndef __W1_LOG_H
-#define __W1_LOG_H
-
-#define DEBUG
-
-#ifdef W1_DEBUG
-# define assert(expr) do {} while (0)
-#else
-# define assert(expr) \
- if(unlikely(!(expr))) { \
- pr_err("Assertion failed! %s,%s,%s,line=%d\n", \
- #expr, __FILE__, __func__, __LINE__); \
- }
-#endif
-
-#endif /* __W1_LOG_H */
-
diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c
index 49e520ca79c5..027950f997d1 100644
--- a/drivers/w1/w1_netlink.c
+++ b/drivers/w1/w1_netlink.c
@@ -18,13 +18,10 @@
#include <linux/connector.h>
#include "w1.h"
-#include "w1_log.h"
#include "w1_netlink.h"
#if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE)))
-#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-
/* Bundle together everything required to process a request in one memory
* allocation.
*/
@@ -598,7 +595,7 @@ static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp)
sizeof(struct w1_netlink_msg) +
sizeof(struct w1_netlink_cmd));
}
- reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size);
+ reply_size = min(CONNECTOR_MAX_MSG_SIZE, reply_size);
/* allocate space for the block, a copy of the original message,
* one node per cmd to point into the original message,
diff --git a/drivers/zorro/zorro-driver.c b/drivers/zorro/zorro-driver.c
index eacae1434b73..fa23b7366b98 100644
--- a/drivers/zorro/zorro-driver.c
+++ b/drivers/zorro/zorro-driver.c
@@ -14,6 +14,8 @@
#include <linux/module.h>
#include <linux/zorro.h>
+#include "zorro.h"
+
/**
* zorro_match_device - Tell if a Zorro device structure has a matching
@@ -161,12 +163,13 @@ static int zorro_uevent(struct device *dev, struct kobj_uevent_env *env)
}
struct bus_type zorro_bus_type = {
- .name = "zorro",
- .dev_name = "zorro",
- .match = zorro_bus_match,
- .uevent = zorro_uevent,
- .probe = zorro_device_probe,
- .remove = zorro_device_remove,
+ .name = "zorro",
+ .dev_name = "zorro",
+ .dev_groups = zorro_device_attribute_groups,
+ .match = zorro_bus_match,
+ .uevent = zorro_uevent,
+ .probe = zorro_device_probe,
+ .remove = zorro_device_remove,
};
EXPORT_SYMBOL(zorro_bus_type);
diff --git a/drivers/zorro/zorro-sysfs.c b/drivers/zorro/zorro-sysfs.c
index 9282dbf5abdb..3d34dba9bb2d 100644
--- a/drivers/zorro/zorro-sysfs.c
+++ b/drivers/zorro/zorro-sysfs.c
@@ -23,33 +23,33 @@
/* show configuration fields */
#define zorro_config_attr(name, field, format_string) \
-static ssize_t \
-show_##name(struct device *dev, struct device_attribute *attr, char *buf) \
+static ssize_t name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
{ \
struct zorro_dev *z; \
\
z = to_zorro_dev(dev); \
return sprintf(buf, format_string, z->field); \
} \
-static DEVICE_ATTR(name, S_IRUGO, show_##name, NULL);
+static DEVICE_ATTR_RO(name);
zorro_config_attr(id, id, "0x%08x\n");
zorro_config_attr(type, rom.er_Type, "0x%02x\n");
zorro_config_attr(slotaddr, slotaddr, "0x%04x\n");
zorro_config_attr(slotsize, slotsize, "0x%04x\n");
-static ssize_t
-show_serial(struct device *dev, struct device_attribute *attr, char *buf)
+static ssize_t serial_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct zorro_dev *z;
z = to_zorro_dev(dev);
return sprintf(buf, "0x%08x\n", be32_to_cpu(z->rom.er_SerialNumber));
}
+static DEVICE_ATTR_RO(serial);
-static DEVICE_ATTR(serial, S_IRUGO, show_serial, NULL);
-
-static ssize_t zorro_show_resource(struct device *dev, struct device_attribute *attr, char *buf)
+static ssize_t resource_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct zorro_dev *z = to_zorro_dev(dev);
@@ -58,8 +58,27 @@ static ssize_t zorro_show_resource(struct device *dev, struct device_attribute *
(unsigned long)zorro_resource_end(z),
zorro_resource_flags(z));
}
+static DEVICE_ATTR_RO(resource);
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct zorro_dev *z = to_zorro_dev(dev);
-static DEVICE_ATTR(resource, S_IRUGO, zorro_show_resource, NULL);
+ return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *zorro_device_attrs[] = {
+ &dev_attr_id.attr,
+ &dev_attr_type.attr,
+ &dev_attr_serial.attr,
+ &dev_attr_slotaddr.attr,
+ &dev_attr_slotsize.attr,
+ &dev_attr_resource.attr,
+ &dev_attr_modalias.attr,
+ NULL
+};
static ssize_t zorro_read_config(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr,
@@ -88,32 +107,17 @@ static struct bin_attribute zorro_config_attr = {
.read = zorro_read_config,
};
-static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
- char *buf)
-{
- struct zorro_dev *z = to_zorro_dev(dev);
-
- return sprintf(buf, ZORRO_DEVICE_MODALIAS_FMT "\n", z->id);
-}
-
-static DEVICE_ATTR(modalias, S_IRUGO, modalias_show, NULL);
+static struct bin_attribute *zorro_device_bin_attrs[] = {
+ &zorro_config_attr,
+ NULL
+};
-int zorro_create_sysfs_dev_files(struct zorro_dev *z)
-{
- struct device *dev = &z->dev;
- int error;
-
- /* current configuration's attributes */
- if ((error = device_create_file(dev, &dev_attr_id)) ||
- (error = device_create_file(dev, &dev_attr_type)) ||
- (error = device_create_file(dev, &dev_attr_serial)) ||
- (error = device_create_file(dev, &dev_attr_slotaddr)) ||
- (error = device_create_file(dev, &dev_attr_slotsize)) ||
- (error = device_create_file(dev, &dev_attr_resource)) ||
- (error = device_create_file(dev, &dev_attr_modalias)) ||
- (error = sysfs_create_bin_file(&dev->kobj, &zorro_config_attr)))
- return error;
-
- return 0;
-}
+static const struct attribute_group zorro_device_attr_group = {
+ .attrs = zorro_device_attrs,
+ .bin_attrs = zorro_device_bin_attrs,
+};
+const struct attribute_group *zorro_device_attribute_groups[] = {
+ &zorro_device_attr_group,
+ NULL
+};
diff --git a/drivers/zorro/zorro.c b/drivers/zorro/zorro.c
index d295d9878dff..cc1b1ac57d61 100644
--- a/drivers/zorro/zorro.c
+++ b/drivers/zorro/zorro.c
@@ -197,9 +197,6 @@ static int __init amiga_zorro_probe(struct platform_device *pdev)
put_device(&z->dev);
continue;
}
- error = zorro_create_sysfs_dev_files(z);
- if (error)
- dev_err(&z->dev, "Error creating sysfs files\n");
}
/* Mark all available Zorro II memory */
diff --git a/drivers/zorro/zorro.h b/drivers/zorro/zorro.h
index 34119fb4e560..4f805c01cfbc 100644
--- a/drivers/zorro/zorro.h
+++ b/drivers/zorro/zorro.h
@@ -5,5 +5,4 @@ extern void zorro_name_device(struct zorro_dev *z);
static inline void zorro_name_device(struct zorro_dev *dev) { }
#endif
-extern int zorro_create_sysfs_dev_files(struct zorro_dev *z);
-
+extern const struct attribute_group *zorro_device_attribute_groups[];