diff --git a/.gitignore b/.gitignore index fd3a355..a36edcc 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,7 @@ all.config # Kdevelop4 *.kdev4 + +# dtb objects +*.dtb +*.dtbo diff --git b/Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr b/Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr new file mode 100644 index 0000000..e2df613 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr @@ -0,0 +1,63 @@ +What: /sys/devices/platform/bone_capemgr/slots +Date: May 2015 +KernelVersion: 4.0 +Contact: Pantelis Antoniou +Description: + READ: + Describe the state of all the slots of the beaglebone capemgr. + Each line of the output describes a slot: + The slot format is as following: + : [P-][F-][O-][l-][L-][D-] \ + ,, + , + + Where the flags are: + P: Slot has been probed + F: Slot has failed probing (i.e. no EEPROM detected) + O: Slot has been overridden by the user + l: Slot is current loading + L: Slot has completed loading and is ready + D: Slot has been disabled + + Example: + 0: P---L- -1 BeagleBone RS232 CAPE,00A1,Beagleboardtoys,BB-BONE-SERL-03 + 1: PF---- -1 + 2: PF---- -1 + 3: PF---- -1 + + WRITE: + Writing a string of the form [:version] issues a request to + load a firmware blob containing an overlay. The name of the firmware blob + is -[version|00A0].dtbo. This act is defined as a slot override. + + Writing a negative slot id removes the slot if it was an overridden one, or + unloads a slot that was probed. + +What: /sys/devices/platform/bone_capemgr/baseboard/ +Date: May 2015 +KernelVersion: 4.0 +Contact: Pantelis Antoniou +Description: Contains the probed base board EEPROM field; one of: + board-name - board-name as stored in cape EEPROM + dc-supplied - whether the cape draws or supplies DC + eeprom-format-revision - EEPROM format rev, only 00A0 supported + header - header; should be 'aa 55 33 ee' + manufacturer - manufacturer string + part-number - part-number of the cape + serial-number - serial number of the cape + version - version of the cape, i.e. 00A0 + number-of-pins - displayed but ignored + pin-usage - displayed but ignored + sys-5v - displayed but ignored + vdd-3v3exp - displayed but ignored + vdd-5v - displayed but ignored +What: /sys/devices/platform/bone_capemgr/slot-/ +Date: May 2015 +KernelVersion: 4.0 +Contact: Pantelis Antoniou +Description: Contains the probed cape's EEPROM field; the field is one of: + board-name - baseboard name i.e. A335BNLT + header - header; should be 'aa 55 33 ee' + revision - baseboard revision + serial-number - baseboard serial number + config-option - displayed but ignored diff --git b/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays b/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays new file mode 100644 index 0000000..88d1549 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays @@ -0,0 +1,52 @@ +What: /sys/firmware/devicetree/overlays/ +Date: October 2015 +Contact: Pantelis Antoniou +Description: + This directory contains the applied device tree overlays of + the running system, as directories of the overlay id. + +What: /sys/firmware/devicetree/overlays/enable +Date: October 2015 +Contact: Pantelis Antoniou +Description: + The master enable switch, by default is 1, and when + set to 0 it cannot be re-enabled for security reasons. + + The discussion about this switch takes place in: + http://comments.gmane.org/gmane.linux.drivers.devicetree/101871 + + Kees Cook: + "Coming from the perspective of drawing a bright line between + kernel and the root user (which tends to start with disabling + kernel module loading), I would say that there at least needs + to be a high-level one-way "off" switch for the interface so + that systems that have this interface can choose to turn it off + during initial boot, etc." + +What: /sys/firmware/devicetree/overlays/ +Date: October 2015 +Contact: Pantelis Antoniou +Description: + Each directory represents an applied overlay, containing + the following attribute files. + +What: /sys/firmware/devicetree/overlays//can_remove +Date: October 2015 +Contact: Pantelis Antoniou +Description: + The attribute set to 1 means that the overlay can be removed, + while 0 means that the overlay is being overlapped therefore + removal is prohibited. + +What: /sys/firmware/devicetree/overlays/// +Date: October 2015 +Contact: Pantelis Antoniou +Description: + Each of these directories contain information about of the + particular overlay fragment. + +What: /sys/firmware/devicetree/overlays///target +Date: October 2015 +Contact: Pantelis Antoniou +Description: + The full-path of the target of the fragment diff --git a/Documentation/devicetree/bindings/arm/omap/omap.txt b/Documentation/devicetree/bindings/arm/omap/omap.txt index 12af302..63555b9 100644 --- a/Documentation/devicetree/bindings/arm/omap/omap.txt +++ b/Documentation/devicetree/bindings/arm/omap/omap.txt @@ -24,6 +24,8 @@ Optional properties: - ti,no-reset-on-init: When present, the module should not be reset at init - ti,no-idle-on-init: When present, the module should not be idled at init - ti,no-idle: When present, the module is never allowed to idle. +- ti,deassert-hard-reset: list of hwmod and hardware reset line name pairs + (ascii strings) to be deasserted upon device instantiation. Example: diff --git b/Documentation/devicetree/bindings/display/etnaviv/etnaviv-drm.txt b/Documentation/devicetree/bindings/display/etnaviv/etnaviv-drm.txt new file mode 100644 index 0000000..ed5e0a7 --- /dev/null +++ b/Documentation/devicetree/bindings/display/etnaviv/etnaviv-drm.txt @@ -0,0 +1,54 @@ +Etnaviv DRM master device +========================= + +The Etnaviv DRM master device is a virtual device needed to list all +Vivante GPU cores that comprise the GPU subsystem. + +Required properties: +- compatible: Should be one of + "fsl,imx-gpu-subsystem" + "marvell,dove-gpu-subsystem" +- cores: Should contain a list of phandles pointing to Vivante GPU devices + +example: + +gpu-subsystem { + compatible = "fsl,imx-gpu-subsystem"; + cores = <&gpu_2d>, <&gpu_3d>; +}; + + +Vivante GPU core devices +======================== + +Required properties: +- compatible: Should be "vivante,gc" + A more specific compatible is not needed, as the cores contain chip + identification registers at fixed locations, which provide all the + necessary information to the driver. +- reg: should be register base and length as documented in the + datasheet +- interrupts: Should contain the cores interrupt line +- clocks: should contain one clock for entry in clock-names + see Documentation/devicetree/bindings/clock/clock-bindings.txt +- clock-names: + - "bus": AXI/register clock + - "core": GPU core clock + - "shader": Shader clock (only required if GPU has feature PIPE_3D) + +Optional properties: +- power-domains: a power domain consumer specifier according to + Documentation/devicetree/bindings/power/power_domain.txt + +example: + +gpu_3d: gpu@00130000 { + compatible = "vivante,gc"; + reg = <0x00130000 0x4000>; + interrupts = <0 9 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks IMX6QDL_CLK_GPU3D_AXI>, + <&clks IMX6QDL_CLK_GPU3D_CORE>, + <&clks IMX6QDL_CLK_GPU3D_SHADER>; + clock-names = "bus", "core", "shader"; + power-domains = <&gpc 1>; +}; diff --git b/Documentation/devicetree/bindings/misc/bone_capemgr.txt b/Documentation/devicetree/bindings/misc/bone_capemgr.txt new file mode 100644 index 0000000..7e4fbc9 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/bone_capemgr.txt @@ -0,0 +1,111 @@ +* Beaglebone cape manager driver + +Required properties: +- compatible: "ti,bone-capemgr" +- eeprom: phandle to the EEPROM baseboard. + The EEPROM framework interface is use to obtain the data. + +Required children nodes: + +- baseboardmaps: Contains nodes, which each of the them defines a mapping from + the baseboard EEPROM board-name ID to a DT friendly compatible + string. + + - board-name: The baseboard EEPROM board name, i.e. A335BONE for the + original beaglebone white. + - compatible-name: The DT friendly compatible string to be used for matching + compatible capes, i.e. "ti,beaglebone" + + + - nvmem-cells: Defines the phandles of the nvmem cells of the baseboard and the + slots. + - nvmem-cells: Defines the names of the nvmem cells. Required to have at + least a baseboard cell name. + + - #slots: Defines how many slots are there. + +- Example of a beaglebone cape-manager: + +bone_capemgr { + compatible = "ti,bone-capemgr"; + status = "okay"; + + nvmem-cell = <&baseboard_data + &cape0_data &cape1_data &cape2_data &cape3_data>; + nvmem-cell-names = "baseboard", "slot0", "slot1", "slot2", "slot3"; + + #slots = <4>; + + /* map board revisions to compatible definitions */ + baseboardmaps { + baseboard_beaglebone: board@0 { + board-name = "A335BONE"; + compatible-name = "ti,beaglebone"; + }; + + baseboard_beaglebone_black: board@1 { + board-name = "A335BNLT"; + compatible-name = "ti,beaglebone-black"; + }; + }; +}; + +The format of the cape to be loaded is in a standard overlay format with +the following root properties that are interpreted by the cape manager: + +Required properties: + - compatible: Should be compatible to the baseboard according to the + baseboard map value, i.e. "ti,beaglebone". + - part-numer: Should contain the part-number as stored in the EEPROM. + - version: Should contain a list of all the version that are supported + by the single cape dtbo, i.e. "00A1". + +Optional properties: + - exclusive-use: A string list which state the resources this cape requires. + No processing or matching to anything regarding the internal + kernel state is performed; it's purpose is to guard against + conflicts with other capes. + - priority: A priority to be assigned when loading a cape. A lower value + has higher priority. The purpose of the priority is to control + which cape is loaded first in case of a conflict. + +- Example of a serial cape: + +/dts-v1/; +/plugin/; +/ { + compatible = "ti,beaglebone", "ti,beaglebone-black"; + + /* identification */ + part-number = "BB-BONE-SERL-03"; + version = "00A1"; + + /* state the resources this cape uses */ + exclusive-use = + /* the pin header uses */ + "P9.21", /* uart2_txd */ + "P9.22", /* uart2_rxd */ + /* the hardware ip uses */ + "uart2"; + + fragment@0 { + target = <&am33xx_pinmux>; + __overlay__ { + bb_uart2_pins: pinmux_bb_uart2_pins { + pinctrl-single,pins = < + 0x150 0x21 /* spi0_sclk.uart2_rxd | MODE1 */ + 0x154 0x01 /* spi0_d0.uart2_txd | MODE1 */ + >; + }; + }; + }; + + fragment@1 { + target = <&uart2>; + __overlay__ { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&bb_uart2_pins>; + }; + }; +}; diff --git b/Documentation/devicetree/bindings/pinctrl/ti,iodelay-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/ti,iodelay-pinctrl.txt new file mode 100644 index 0000000..e12f4e5 --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/ti,iodelay-pinctrl.txt @@ -0,0 +1,86 @@ +Texas Instruments I/O Delay module configuration pinctrl definition + +Used in conjunction with Documentation/devicetree/bindings/pinctrl/ti,omap-pinctrl.txt + +Required Properties: +- compatible: Should be: + "ti,dra7-iodelay" - I/O delay configuration for DRA7 +- reg - must be the register address range of IODelay module +- #address-cells = <1>; +- #size-cells = <0>; + +Important note: Use of "ti,dra7-iodelay" compatible definition need to be +carefully evaluated due to the expectation of glitch during configuration. + +Example: + +dra7_iodelay_core: padconf@4844a000 { + compatible = "ti,dra7-iodelay"; + reg = <0x4844a000 0x0d1c>; + #address-cells = <1>; + #size-cells = <0>; +}; + +Configuration definition follows similar model as the pinctrl-single: +The groups of pin configuration are defined under "pinctrl-single,pins" + +&dra7_iodelay_core { + mmc2_iodelay_3v3_conf: mmc2_iodelay_3v3_conf { + pinctrl-single,pins = < + 0x18c (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A19_IN */ + 0x1a4 (A_DELAY(265) | G_DELAY(360)) /* CFG_GPMC_A20_IN */ + 0x1b0 (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A21_IN */ + 0x1bc (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A22_IN */ + 0x1c8 (A_DELAY(287) | G_DELAY(420)) /* CFG_GPMC_A23_IN */ + 0x1d4 (A_DELAY(144) | G_DELAY(240)) /* CFG_GPMC_A24_IN */ + 0x1e0 (A_DELAY(0) | G_DELAY(0)) /* CFG_GPMC_A25_IN */ + 0x1ec (A_DELAY(120) | G_DELAY(0)) /* CFG_GPMC_A26_IN */ + 0x1f8 (A_DELAY(120) | G_DELAY(180)) /* CFG_GPMC_A27_IN */ + 0x360 (A_DELAY(0) | G_DELAY(0)) /* CFG_GPMC_CS1_IN */ + >; + }; +}; + +Usage in conjunction with pinctrl single: + +For a complete description of the pins both the regular muxing as well as the +iodelay configuration is necessary. For example: + +&dra7_pmx_core { + mmc2_pins_default: mmc2_pins_default { + pinctrl-single,pins = < + 0x9c (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a23.mmc2_clk */ + 0xb0 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_cs1.mmc2_cmd */ + 0xa0 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a24.mmc2_dat0 */ + 0xa4 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a25.mmc2_dat1 */ + 0xa8 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a26.mmc2_dat2 */ + 0xac (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a27.mmc2_dat3 */ + 0x8c (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a19.mmc2_dat4 */ + 0x90 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a20.mmc2_dat5 */ + 0x94 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a21.mmc2_dat6 */ + 0x98 (PIN_INPUT_PULLUP | MANUAL_MODE | MUX_MODE1) /* gpmc_a22.mmc2_dat7 */ + >; + }; +}; + +&dra7_iodelay_core { + mmc2_iodelay_3v3_conf: mmc2_iodelay_3v3_conf { + pinctrl-single,pins = < + 0x18c (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A19_IN */ + 0x1a4 (A_DELAY(265) | G_DELAY(360)) /* CFG_GPMC_A20_IN */ + 0x1b0 (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A21_IN */ + 0x1bc (A_DELAY(0) | G_DELAY(120)) /* CFG_GPMC_A22_IN */ + 0x1c8 (A_DELAY(287) | G_DELAY(420)) /* CFG_GPMC_A23_IN */ + 0x1d4 (A_DELAY(144) | G_DELAY(240)) /* CFG_GPMC_A24_IN */ + 0x1e0 (A_DELAY(0) | G_DELAY(0)) /* CFG_GPMC_A25_IN */ + 0x1ec (A_DELAY(120) | G_DELAY(0)) /* CFG_GPMC_A26_IN */ + 0x1f8 (A_DELAY(120) | G_DELAY(180)) /* CFG_GPMC_A27_IN */ + 0x360 (A_DELAY(0) | G_DELAY(0)) /* CFG_GPMC_CS1_IN */ + >; + }; +}; + +&mmc2 { + pinctrl-names = "default"; + pinctrl-0 = <&mmc2_pins_default &mmc2_iodelay_3v3_conf>; +}; diff --git b/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt b/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt new file mode 100644 index 0000000..5befb53 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-omap-dmtimer.txt @@ -0,0 +1,18 @@ +* OMAP PWM for dual-mode timers + +Required properties: +- compatible: Shall contain "ti,omap-dmtimer-pwm". +- ti,timers: phandle to PWM capable OMAP timer. See arm/omap/timer.txt for info + about these timers. +- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of + the cells format. + +Optional properties: +- ti,prescaler: Should be a value between 0 and 7, see the timers datasheet + +Example: + pwm9: dmtimer-pwm@9 { + compatible = "ti,omap-dmtimer-pwm"; + ti,timers = <&timer9>; + #pwm-cells = <3>; + }; diff --git b/Documentation/devicetree/bindings/usb/generic-onboard-device.txt b/Documentation/devicetree/bindings/usb/generic-onboard-device.txt new file mode 100644 index 0000000..4dd7bca --- /dev/null +++ b/Documentation/devicetree/bindings/usb/generic-onboard-device.txt @@ -0,0 +1,31 @@ +Generic Onboard USB Device + +The node should be located at USB host controller's node or +any USB HUB's node. + +Required properties: +- compatible: should be "generic-onboard-device" + +Optional properties: +- clocks: the input clock for USB device. +- clock-frequency: the frequency for device's clock. +- reset-gpios: Should specify the GPIO for reset. +- reset-duration-us: the duration for assert reset signal, the time unit + is microsecond. + +Example: + +&usbh1 { + vbus-supply = <®_usb_h1_vbus>; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + hub: usb2415@01 { + compatible = "generic-onboard-device"; + reg = <0x01>; + clocks = <&clks IMX6QDL_CLK_CKO>; + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + reset-duration-us = <10>; + }; +}; diff --git b/Documentation/devicetree/configfs-overlays.txt b/Documentation/devicetree/configfs-overlays.txt new file mode 100644 index 0000000..5fa43e0 --- /dev/null +++ b/Documentation/devicetree/configfs-overlays.txt @@ -0,0 +1,31 @@ +Howto use the configfs overlay interface. + +A device-tree configfs entry is created in /config/device-tree/overlays +and and it is manipulated using standard file system I/O. +Note that this is a debug level interface, for use by developers and +not necessarily something accessed by normal users due to the +security implications of having direct access to the kernel's device tree. + +* To create an overlay you mkdir the directory: + + # mkdir /config/device-tree/overlays/foo + +* Either you echo the overlay firmware file to the path property file. + + # echo foo.dtbo >/config/device-tree/overlays/foo/path + +* Or you cat the contents of the overlay to the dtbo file + + # cat foo.dtbo >/config/device-tree/overlays/foo/dtbo + +The overlay file will be applied, and devices will be created/destroyed +as required. + +To remove it simply rmdir the directory. + + # rmdir /config/device-tree/overlays/foo + +The rationalle of the dual interface (firmware & direct copy) is that each is +better suited to different use patterns. The firmware interface is what's +intended to be used by hardware managers in the kernel, while the copy interface +make sense for developers (since it avoids problems with namespaces). diff --git a/Documentation/devicetree/overlay-notes.txt b/Documentation/devicetree/overlay-notes.txt index d418a6c..00ede57 100644 --- a/Documentation/devicetree/overlay-notes.txt +++ b/Documentation/devicetree/overlay-notes.txt @@ -100,6 +100,14 @@ Finally, if you need to remove all overlays in one-go, just call of_overlay_destroy_all() which will remove every single one in the correct order. +If your board has multiple slots/places where a single overlay can work +and each slot is defined by a node, you can use the of_overlay_create_indirect() +method to select the target. + +For overlays on probeable busses, use the of_overlay_create_target_root() method +in which you supply a device node as a target root, and which all target +references in the overlay are performed relative to that node. + Overlay DTS Format ------------------ @@ -113,6 +121,11 @@ The DTS of an overlay should have the following format: target=; /* phandle target of the overlay */ or target-path="/path"; /* target path of the overlay */ + or + target-indirect { /* indirect target selector */ + foo { target|target-path ... }; + bar { .... }; + }; __overlay__ { property-a; /* add property-a to the target */ @@ -131,3 +144,11 @@ Using the non-phandle based target method allows one to use a base DT which does not contain a __symbols__ node, i.e. it was not compiled with the -@ option. The __symbols__ node is only required for the target= method, since it contains the information required to map from a phandle to a tree location. + +The indirect target requires the use of a selector target on the call to +of_overlay_create_indirect(). I.e. passing the "foo" id will select the target +in the foo node, "bar" in bar node, etc. + +Note that when using the target root create method all target references must +lie under the target root node. I.e. the overlay is not allowed to 'break' out +of the root. diff --git a/Documentation/filesystems/configfs/configfs.txt b/Documentation/filesystems/configfs/configfs.txt index af68efd..e5fe521 100644 --- a/Documentation/filesystems/configfs/configfs.txt +++ b/Documentation/filesystems/configfs/configfs.txt @@ -51,15 +51,27 @@ configfs tree is always there, whether mounted on /config or not. An item is created via mkdir(2). The item's attributes will also appear at this time. readdir(3) can determine what the attributes are, read(2) can query their default values, and write(2) can store new -values. Like sysfs, attributes should be ASCII text files, preferably -with only one value per file. The same efficiency caveats from sysfs -apply. Don't mix more than one attribute in one attribute file. - -Like sysfs, configfs expects write(2) to store the entire buffer at -once. When writing to configfs attributes, userspace processes should -first read the entire file, modify the portions they wish to change, and -then write the entire buffer back. Attribute files have a maximum size -of one page (PAGE_SIZE, 4096 on i386). +values. Don't mix more than one attribute in one attribute file. + +There are two types of configfs attributes: + +* Normal attributes, which similar to sysfs attributes, are small ASCII text +files, with a maximum size of one page (PAGE_SIZE, 4096 on i386). Preferably +only one value per file should be used, and the same caveats from sysfs apply. +Configfs expects write(2) to store the entire buffer at once. When writing to +normal configfs attributes, userspace processes should first read the entire +file, modify the portions they wish to change, and then write the entire +buffer back. + +* Binary attributes, which are somewhat similar to sysfs binary attributes, +but with a few slight changes to semantics. The PAGE_SIZE limitation does not +apply, but the whole binary item must fit in single kernel vmalloc'ed buffer. +The write(2) calls from user space are buffered, and the attributes' +write_bin_attribute method will be invoked on the final close, therefore it is +imperative for user-space to check the return code of close(2) in order to +verify that the operation finished successfully. +To avoid a malicious user OOMing the kernel, there's a per-binary attribute +maximum buffer value. When an item needs to be destroyed, remove it with rmdir(2). An item cannot be destroyed if any other item has a link to it (via @@ -171,6 +183,7 @@ among other things. For that, it needs a type. struct configfs_item_operations *ct_item_ops; struct configfs_group_operations *ct_group_ops; struct configfs_attribute **ct_attrs; + struct configfs_bin_attribute **ct_bin_attrs; }; The most basic function of a config_item_type is to define what @@ -201,6 +214,32 @@ be called whenever userspace asks for a read(2) on the attribute. If an attribute is writable and provides a ->store method, that method will be be called whenever userspace asks for a write(2) on the attribute. +[struct configfs_bin_attribute] + + struct configfs_attribute { + struct configfs_attribute cb_attr; + void *cb_private; + size_t cb_max_size; + }; + +The binary attribute is used when the one needs to use binary blob to +appear as the contents of a file in the item's configfs directory. +To do so add the binary attribute to the NULL-terminated array +config_item_type->ct_bin_attrs, and the item appears in configfs, the +attribute file will appear with the configfs_bin_attribute->cb_attr.ca_name +filename. configfs_bin_attribute->cb_attr.ca_mode specifies the file +permissions. +The cb_private member is provided for use by the driver, while the +cb_max_size member specifies the maximum amount of vmalloc buffer +to be used. + +If binary attribute is readable and the config_item provides a +ct_item_ops->read_bin_attribute() method, that method will be called +whenever userspace asks for a read(2) on the attribute. The converse +will happen for write(2). The reads/writes are bufferred so only a +single read/write will occur; the attributes' need not concern itself +with it. + [struct config_group] A config_item cannot live in a vacuum. The only way one can be created diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 0e4102a..4143a9d 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -90,6 +90,7 @@ parameter is applicable: NET Appropriate network support is enabled. NUMA NUMA support is enabled. NFS Appropriate NFS support is enabled. + OF Open Firmware support (device tree) is enabled. OSS OSS sound support is enabled. PV_OPS A paravirtualized kernel is enabled. PARIDE The ParIDE (parallel port IDE) subsystem is enabled. @@ -2618,6 +2619,8 @@ bytes respectively. Such letter suffixes can also be entirely omitted. This can be set from sysctl after boot. See Documentation/sysctl/vm.txt for details. + of_overlay_disable [OF] Disable device tree overlays at boot time. + ohci1394_dma=early [HW] enable debugging via the ohci1394 driver. See Documentation/debugging-via-ohci1394.txt for more info. diff --git b/Documentation/misc-devices/bone_capemgr.txt b/Documentation/misc-devices/bone_capemgr.txt new file mode 100644 index 0000000..2a8c766 --- /dev/null +++ b/Documentation/misc-devices/bone_capemgr.txt @@ -0,0 +1,63 @@ +--------------------------- + Beaglebone Cape-Manager +--------------------------- + +The beaglebone cape manager driver allows the automatic use of external +peripheral capes to be automatically supported by Linux without any manual +setup required by the user. + +Each beaglebone cape should contain an EEPROM that describes +it in a fixed I2C address on the i2c2 bus of the baseboard. +The format of the EEPROM is defined in the beaglebone reference +manual at: +http://beagleboard.org/static/beaglebone/latest/Docs/Hardware/BONE_SRM.pdf + +Reading the part number and revision information the manager +requests a firmware file formatted as a device tree overlay blob. + +Applying the overlay the devices are instantiated and the cape is +ready to be used. + +For instance if the part-number is BB-BONE-SERL-03 and the version is 00A1 +the firmware file requested will be BB-BONE-SERL-03-00A1-00A1.dtbo +It will be located by the in-kernel firmware +loader in the usual place, i.e. /lib/firmware/`uname -r`, /lib/firmware etc. + +The driver supports the following parameters (either as part of the kernel +command line or supplied at module insertion time). + +disable_partno: A comma delimited list of PART-NUMBER[:REV] of + disabled capes. +enable_partno: A comma delimited list of PART-NUMBER[:REV[:PRIO]] of + enabled capes. +boot_scan_period: The boot scan period in ms. When the cape manager is built-in + the kernel image, the firmware loader cannot find the files + before the rootfs is mounted. This parameter controls the + period with which the boot state is checked in that case. + +There's a sysfs control interface which is defined at the ABI documentation +area. + +Theory of operation: +-------------------- + +On driver probe the I2C EEPROM of the baseboard is read and information about +the current baseboard is retrieved. This information includes the mapping from +baseboard board name to DT friendly compatible string. I.e. the "A335BONE" board +name from EEPROM is mapped to the "ti,beaglebone" compatible string which should +be present in the dtbo to be loaded. + +Afterwards the EEPROMs declared in each slot are probed, and the EEPROMs found +are decoded keeping track the cape part-number and version data. + +Using the part-number and version a firmware file is requested (the firmware +file requested is -.dtbo). + +The dtbo is unflattend and the resulting device tree is matched against a +compatible baseboard, and in case of multiple parallel loading capes the +priorities defined are honored. That means that when there are multiple capes +being loaded in parallel the ones with the lowest priority number are loaded +first. + +Applying the device tree overlay makes the cape operational, as if it was part +of the kernel's booting device tree. diff --git a/Documentation/printk-formats.txt b/Documentation/printk-formats.txt index b784c27..5bf7026 100644 --- a/Documentation/printk-formats.txt +++ b/Documentation/printk-formats.txt @@ -309,10 +309,40 @@ Command from struct task_struct Passed by reference. +Device tree nodes: + + %pO[fnpPcCFr] + + For printing device tree nodes. The optional arguments are: + f device node full_name + n device node name + p device node phandle + P device node path spec (name + @unit) + F device node flags + c major compatible string + C full compatible string + r node reference count + Without any arguments prints full_name (same as %pOf) + The separator when using multiple arguments is '|' + + Examples: + + %pO /foo/bar@0 - Node full name + %pOf /foo/bar@0 - Same as above + %pOfp /foo/bar@0|10 - Node full name + phandle + %pOfcF /foo/bar@0|foo,device|--P- - Node full name + + major compatible string + + node flags + D - dynamic + d - detached + P - Populated + B - Populated bus + + Passed by reference + If you add other %p extensions, please extend lib/test_printf.c with one or more test cases, if at all feasible. - Thank you for your cooperation and attention. diff --git a/MAINTAINERS b/MAINTAINERS index ab65bbe..74721a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2130,6 +2130,14 @@ W: http://linuxtv.org S: Supported F: drivers/media/platform/sti/bdisp +BEAGLEBONE CAPEMANAGER +M: Pantelis Antoniou +S: Maintained +F: drivers/misc/beaglebone-capemgr.c +F: Documentation/misc-devices/bone_capemgr.txt +F: Documentation/devicetree/bindings/misc/bone_capemgr.txt +F: Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr + BEFS FILE SYSTEM S: Orphan F: Documentation/filesystems/befs.txt @@ -3744,6 +3752,15 @@ S: Maintained F: drivers/gpu/drm/sti F: Documentation/devicetree/bindings/display/st,stih4xx.txt +DRM DRIVERS FOR VIVANTE GPU IP +M: Lucas Stach +R: Russell King +R: Christian Gmeiner +L: dri-devel@lists.freedesktop.org +S: Maintained +F: drivers/gpu/drm/etnaviv +F: Documentation/devicetree/bindings/display/etnaviv + DSBR100 USB FM RADIO DRIVER M: Alexey Klimov L: linux-media@vger.kernel.org @@ -11157,6 +11174,13 @@ S: Maintained F: Documentation/usb/ohci.txt F: drivers/usb/host/ohci* +USB Generic Onboard Device Driver +M: Peter Chen +T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git +L: linux-usb@vger.kernel.org +S: Maintained +F: drivers/usb/misc/generic_onboard_device.c + USB OTG FSM (Finite State Machine) M: Peter Chen T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index 9eca7ae..d4273d4 100644 --- a/arch/arm/boot/Makefile +++ b/arch/arm/boot/Makefile @@ -27,6 +27,10 @@ export ZRELADDR INITRD_PHYS PARAMS_PHYS targets := Image zImage xipImage bootpImage uImage +ifeq ($(CONFIG_OF_OVERLAY),y) +DTC_FLAGS += -@ +endif + ifeq ($(CONFIG_XIP_KERNEL),y) $(obj)/xipImage: vmlinux FORCE diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 30bbc37..ee9ec65 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -1,9 +1,15 @@ ifeq ($(CONFIG_OF),y) +ifeq ($(CONFIG_OF_OVERLAY),y) +DTC_FLAGS += -@ +endif + dtb-$(CONFIG_ARCH_ALPINE) += \ alpine-db.dtb + dtb-$(CONFIG_MACH_ASM9260) += \ alphascale-asm9260-devkit.dtb + # Keep at91 dtb files sorted alphabetically for each SoC dtb-$(CONFIG_SOC_SAM_V4_V5) += \ at91rm9200ek.dtb \ @@ -302,6 +308,7 @@ dtb-$(CONFIG_SOC_IMX6Q) += \ imx6dl-sabreauto.dtb \ imx6dl-sabrelite.dtb \ imx6dl-sabresd.dtb \ + imx6dl-sabresd-wl1835.dtb \ imx6dl-tx6dl-comtft.dtb \ imx6dl-tx6u-801x.dtb \ imx6dl-tx6u-811x.dtb \ @@ -311,6 +318,7 @@ dtb-$(CONFIG_SOC_IMX6Q) += \ imx6q-apf6dev.dtb \ imx6q-arm2.dtb \ imx6q-cm-fx6.dtb \ + imx6q-ccimx6sbc.dtb \ imx6q-cubox-i.dtb \ imx6q-dfi-fs700-m60.dtb \ imx6q-dmo-edmqmx6.dtb \ @@ -330,6 +338,7 @@ dtb-$(CONFIG_SOC_IMX6Q) += \ imx6q-sabreauto.dtb \ imx6q-sabrelite.dtb \ imx6q-sabresd.dtb \ + imx6q-sabresd-wl1835.dtb \ imx6q-sbc6x.dtb \ imx6q-tbs2910.dtb \ imx6q-tx6q-1010.dtb \ @@ -342,6 +351,7 @@ dtb-$(CONFIG_SOC_IMX6Q) += \ imx6q-wandboard-revb1.dtb dtb-$(CONFIG_SOC_IMX6SL) += \ imx6sl-evk.dtb \ + imx6sl-evk-wl1835.dtb \ imx6sl-warp.dtb dtb-$(CONFIG_SOC_IMX6SX) += \ imx6sx-sabreauto.dtb \ @@ -458,6 +468,23 @@ dtb-$(CONFIG_SOC_AM33XX) += \ am335x-base0033.dtb \ am335x-bone.dtb \ am335x-boneblack.dtb \ + am335x-sancloud-bbe.dtb \ + am335x-boneblack-audio.dtb \ + am335x-boneblack-bbb-exp-r.dtb \ + am335x-boneblack-bbb-exp-c.dtb \ + am335x-boneblack-bbbmini.dtb \ + am335x-boneblack-wl1835mod.dtb \ + am335x-boneblack-cape-bone-argus.dtb \ + am335x-bone-cape-bone-argus.dtb \ + am335x-arduino-tre.dtb \ + am335x-bonegreen-wireless.dtb \ + am335x-olimex-som.dtb \ + am335x-abbbi.dtb \ + am335x-bonegreen-overlay.dtb \ + am335x-boneblack-overlay.dtb \ + am335x-boneblack-nhdmi-overlay.dtb \ + am335x-boneblack-hdmi-overlay.dtb \ + am335x-boneblack-emmc-overlay.dtb \ am335x-bonegreen.dtb \ am335x-sl50.dtb \ am335x-evm.dtb \ @@ -472,6 +499,7 @@ dtb-$(CONFIG_ARCH_OMAP4) += \ omap4-panda.dtb \ omap4-panda-a4.dtb \ omap4-panda-es.dtb \ + omap4-panda-es-b3.dtb \ omap4-sdp.dtb \ omap4-sdp-es23plus.dtb \ omap4-var-dvk-om44.dtb \ diff --git b/arch/arm/boot/dts/am335x-abbbi.dts b/arch/arm/boot/dts/am335x-abbbi.dts new file mode 100644 index 0000000..2980478 --- /dev/null +++ b/arch/arm/boot/dts/am335x-abbbi.dts @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright 2015 Konsulko Group + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "Arrow BeagleBone Black Industrial"; + compatible = "arrow,am335x-abbbi", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +&am33xx_pinmux { + adi_hdmi_bbbi_pins: adi_hdmi_bbbi_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + AM33XX_IOPAD(0x8a0, PIN_OUTPUT | MUX_MODE0) /* lcd_data0.lcd_data0 */ + AM33XX_IOPAD(0x8a4, PIN_OUTPUT | MUX_MODE0) /* lcd_data1.lcd_data1 */ + AM33XX_IOPAD(0x8a8, PIN_OUTPUT | MUX_MODE0) /* lcd_data2.lcd_data2 */ + AM33XX_IOPAD(0x8ac, PIN_OUTPUT | MUX_MODE0) /* lcd_data3.lcd_data3 */ + AM33XX_IOPAD(0x8b0, PIN_OUTPUT | MUX_MODE0) /* lcd_data4.lcd_data4 */ + AM33XX_IOPAD(0x8b4, PIN_OUTPUT | MUX_MODE0) /* lcd_data5.lcd_data5 */ + AM33XX_IOPAD(0x8b8, PIN_OUTPUT | MUX_MODE0) /* lcd_data6.lcd_data6 */ + AM33XX_IOPAD(0x8bc, PIN_OUTPUT | MUX_MODE0) /* lcd_data7.lcd_data7 */ + AM33XX_IOPAD(0x8c0, PIN_OUTPUT | MUX_MODE0) /* lcd_data8.lcd_data8 */ + AM33XX_IOPAD(0x8c4, PIN_OUTPUT | MUX_MODE0) /* lcd_data9.lcd_data9 */ + AM33XX_IOPAD(0x8c8, PIN_OUTPUT | MUX_MODE0) /* lcd_data10.lcd_data10 */ + AM33XX_IOPAD(0x8cc, PIN_OUTPUT | MUX_MODE0) /* lcd_data11.lcd_data11 */ + AM33XX_IOPAD(0x8d0, PIN_OUTPUT | MUX_MODE0) /* lcd_data12.lcd_data12 */ + AM33XX_IOPAD(0x8d4, PIN_OUTPUT | MUX_MODE0) /* lcd_data13.lcd_data13 */ + AM33XX_IOPAD(0x8d8, PIN_OUTPUT | MUX_MODE0) /* lcd_data14.lcd_data14 */ + AM33XX_IOPAD(0x8dc, PIN_OUTPUT | MUX_MODE0) /* lcd_data15.lcd_data15 */ + AM33XX_IOPAD(0x8e0, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_vsync.lcd_vsync */ + AM33XX_IOPAD(0x8e4, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_hsync.lcd_hsync */ + AM33XX_IOPAD(0x8e8, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_pclk.lcd_pclk */ + AM33XX_IOPAD(0x8ec, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_ac_bias_en.lcd_ac_bias_en */ + >; + }; + adi_hdmi_bbbi_off_pins: adi_hdmi_bbbi_off_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + >; + }; + + mcasp0_pins: mcasp0_pins { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLUP | MUX_MODE0) /* mcasp0_ahclkx.mcasp0_ahclkx */ + 0x19c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mcasp0_ahclkr.mcasp0_axr2 */ + 0x194 (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mcasp0_fsx.mcasp0_fsx */ + 0x190 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx.mcasp0_aclkx */ + 0x06c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a11.GPIO1_27 */ + >; + }; + + mcasp0_pins_sleep: mcasp0_pins_sleep { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_ahclkx.mcasp0_ahclkx */ + 0x19c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_ahclkr.mcasp0_axr2 */ + 0x194 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_fsx.mcasp0_fsx */ + 0x190 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_aclkx.mcasp0_aclkx */ + 0x06c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_a11.GPIO1_27 */ + >; + }; +}; + +&lcdc { + status = "okay"; + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; +}; + +&i2c0 { + adv7511w { + compatible = "adi,adv7511w"; + reg = <0x39>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&adi_hdmi_bbbi_pins>; + pinctrl-1 = <&adi_hdmi_bbbi_off_pins>; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; + }; + }; +}; + +&mcasp0 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&mcasp0_pins>; + pinctrl-1 = <&mcasp0_pins_sleep>; + status = "okay"; + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ + 0 0 1 0 + >; + tx-num-evt = <1>; + rx-num-evt = <1>; +}; + +/ { + clk_mcasp0_fixed: clk_mcasp0_fixed { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24576000>; + }; + + clk_mcasp0: clk_mcasp0 { + #clock-cells = <0>; + compatible = "gpio-gate-clock"; + clocks = <&clk_mcasp0_fixed>; + enable-gpios = <&gpio1 27 0>; /* BeagleBone Black Clk enable on GPIO1_27 */ + }; + + hdmi_audio: hdmi_audio@0 { + compatible = "linux,hdmi-audio"; + status = "okay"; + }; + + sound { + compatible = "ti,beaglebone-black-audio"; + ti,model = "TI BeagleBone Black"; + ti,audio-codec = <&hdmi_audio>; + ti,mcasp-controller = <&mcasp0>; + ti,audio-routing = + "HDMI Out", "TX"; + clocks = <&clk_mcasp0>; + clock-names = "mclk"; + }; +}; + +&rtc { + system-power-controller; +}; diff --git b/arch/arm/boot/dts/am335x-arduino-tre.dts b/arch/arm/boot/dts/am335x-arduino-tre.dts new file mode 100644 index 0000000..0e6b364 --- /dev/null +++ b/arch/arm/boot/dts/am335x-arduino-tre.dts @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" + +/ { + model = "TI AM335x Arduino Tre"; + compatible = "ti,am335x-arduino-tre", "ti,am335x-boneblack", "ti,am335x-bone", "ti,am33xx"; + + cpus { + cpu@0 { + cpu0-supply = <&dcdc2_reg>; + }; + }; + + memory { + device_type = "memory"; + reg = <0x80000000 0x10000000>; /* 512 MB */ + }; + + leds { + pinctrl-names = "default"; + pinctrl-0 = <&userled_pins>; + + compatible = "gpio-leds"; + + led@0 { + label = "arduino_tre:yel:usr0"; + gpios = <&gpio1 21 0>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + + led@1 { + label = "arduino_tre:red:usr1"; + gpios = <&gpio1 22 0>; + linux,default-trigger = "none"; + default-state = "off"; + }; + + led@2 { + label = "arduino_tre:blu:usr2"; + gpios = <&gpio1 23 0>; + linux,default-trigger = "none"; + default-state = "off"; + }; + + led@3 { + label = "arduino_tre:grn:usr3"; + gpios = <&gpio1 24 0>; + linux,default-trigger = "cpu0"; + default-state = "off"; + }; + }; + + hdmi { + compatible = "ti,tilcdc,slave"; + i2c = <&i2c0>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + status = "okay"; + }; + + sound { + compatible = "ti,da830-evm-audio"; + ti,model = "DA830 EVM"; + ti,audio-codec = <&tlv320aic3x>; + ti,mcasp-controller = <&mcasp0>; + ti,codec-clock-rate = <12000000>; + ti,audio-routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "LINE2L", "Line In", + "LINE2R", "Line In"; + }; + + vmmcsd_fixed: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "vmmcsd_fixed"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; +}; + +&am33xx_pinmux { + pinctrl-names = "default"; + pinctrl-0 = <&userled_pins>; + + userled_pins: pinmux_userled_pins { + pinctrl-single,pins = < + 0x54 0x7 /* gpmc_a5.gpio1_21, OUTPUT | MODE7 */ + 0x58 0x17 /* gpmc_a6.gpio1_22, OUTPUT_PULLUP | MODE7 */ + 0x5c 0x7 /* gpmc_a7.gpio1_23, OUTPUT | MODE7 */ + 0x60 0x17 /* gpmc_a8.gpio1_24, OUTPUT_PULLUP | MODE7 */ + >; + }; + + can_bus_pins: pinmux_can_bus_pins { + pinctrl-single,pins = < + 0x120 0x31 /* DCAN0_RX MODE1 */ + 0x11c 0x01 /* DCAN0_TX MODE1 */ + >; + }; + + cpsw_default: cpsw_default { + pinctrl-single,pins = < + /* Slave 1 */ + 0x110 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ + 0x114 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ + 0x118 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ + 0x11c (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ + 0x120 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ + 0x124 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ + 0x128 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ + 0x12c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ + 0x130 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ + 0x134 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ + 0x138 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ + 0x13c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ + 0x140 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ + >; + }; + + cpsw_sleep: cpsw_sleep { + pinctrl-single,pins = < + /* Slave 1 reset value */ + 0x110 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x114 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x11c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x120 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x124 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x128 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x12c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x130 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + emac_rmii1_pins: pinmux_emac_rmii1_pins { + pinctrl-single,pins = < + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE1) /* mii1_crs.rmii1_crs_dv */ + 0x110 (PIN_INPUT_PULLDOWN | MUX_MODE1) /* mii1_rxerr.rmii1_rxerr */ + 0x114 (PIN_OUTPUT | MUX_MODE1) /* mii1_txen.rmii1_txen */ + 0x124 (PIN_OUTPUT | MUX_MODE1) /* mii1_txd1.rmii1_txd1 */ + 0x128 (PIN_OUTPUT | MUX_MODE1) /* mii1_txd0.rmii1_txd0 */ + 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE1) /* mii1_rxd1.rmii1_rxd1 */ + 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE1) /* mii1_rxd0.rmii1_rxd0 */ + 0x144 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* rmii1_refclk.rmii1_refclk */ + >; + }; + + davinci_mdio_default: davinci_mdio_default { + pinctrl-single,pins = < + /* MDIO */ + 0x148 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + 0x14c (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + >; + }; + + davinci_mdio_sleep: davinci_mdio_sleep { + pinctrl-single,pins = < + /* MDIO reset value */ + 0x148 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x14c (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + i2c0_pins: pinmux_i2c0_pins { + pinctrl-single,pins = < + 0x188 0x70 /* i2c0_sda, SLEWCTRL_SLOW | INPUT_PULLUP | MODE0 */ + 0x18c 0x70 /* i2c0_scl, SLEWCTRL_SLOW | INPUT_PULLUP | MODE0 */ + >; + }; + + i2c1_pins: pinmux_i2c1_pins { + pinctrl-single,pins = < + 0x158 0x72 /*spi0_d1-i2c1_sda,SLEWCTRL_SLOW | INPUT_PULLUP | MODE2*/ + 0x15c 0x72 /*spi0_cs0-i2c1_scl,SLEWCTRL_SLOW | INPUT_PULLUP |MODE2*/ + >; + }; + + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + 0x150 0x72 /*spi0_scl.i2c2_sda,SLEWCTRL_SLOW | INPUT_PULLUP |MODE2*/ + 0x154 0x72 /*spi0_d0.i2c2_scl,SLEWCTRL_SLOW | INPUT_PULLUP | MODE2*/ + >; + }; + + mmc1_pins_default: pinmux_mmc1_pins { + pinctrl-single,pins = < + 0x0F0 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat3.mmc0_dat3 */ + 0x0F4 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat2.mmc0_dat2 */ + 0x0F8 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat1.mmc0_dat1 */ + 0x0FC (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat0.mmc0_dat0 */ + 0x100 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_clk.mmc0_clk */ + 0x104 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_cmd.mmc0_cmd */ + 0x1A0 (PIN_INPUT_PULLUP | MUX_MODE7) /* mcasp0_aclkr.gpio3_18 */ + 0x160 (PIN_INPUT | MUX_MODE7) /* spi0_cs1.gpio0_6 */ + >; + }; + + mmc1_pins_sleep: pinmux_mmc1_pins_sleep { + pinctrl-single,pins = < + 0x0F0 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0F4 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0F8 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0FC (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x100 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x104 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x1A0 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + tre_ehrpwm1_pins: pinmux_tre_ehrpwm1_pins { + pinctrl-single,pins = < + 0x48 0x06 /* PWM1A ~102 MODE6 */ + 0x4c 0x06 /* PWM1B ~103 MODE6 */ + >; + }; + + tre_ehrpwm2_pins: pinmux_tre_ehrpwm2_pins { + pinctrl-single,pins = < + 0x20 0x04 /* PWM2A ~100 MODE4 */ + 0x24 0x04 /* PWM2B ~101 MODE4 */ + >; + }; + + nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + 0xa0 0x08 /* lcd_data0.lcd_data0, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xa4 0x08 /* lcd_data1.lcd_data1, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xa8 0x08 /* lcd_data2.lcd_data2, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xac 0x08 /* lcd_data3.lcd_data3, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb0 0x08 /* lcd_data4.lcd_data4, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb4 0x08 /* lcd_data5.lcd_data5, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb8 0x08 /* lcd_data6.lcd_data6, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xbc 0x08 /* lcd_data7.lcd_data7, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc0 0x08 /* lcd_data8.lcd_data8, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc4 0x08 /* lcd_data9.lcd_data9, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc8 0x08 /* lcd_data10.lcd_data10, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xcc 0x08 /* lcd_data11.lcd_data11, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd0 0x08 /* lcd_data12.lcd_data12, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd4 0x08 /* lcd_data13.lcd_data13, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd8 0x08 /* lcd_data14.lcd_data14, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xdc 0x08 /* lcd_data15.lcd_data15, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xe0 0x00 /* lcd_vsync.lcd_vsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xe4 0x00 /* lcd_hsync.lcd_hsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xe8 0x00 /* lcd_pclk.lcd_pclk, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xec 0x00 /* lcd_ac_bias_en.lcd_ac_bias_en, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + >; + }; + + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; + + tre_audio_pins: pinmux_tre_audio_pins { + pinctrl-single,pins = < + 0x1ac 0x00 /*mcasp0_ahclkx (AUD_MCLK)->12MHz, INPUT | MODE0*/ + 0x190 0x20 /* mcasp0_aclkx (AUD_BCLK)->, INPUT | MODE0*/ + 0x194 0x20 /* mcasp0_fsx (AUD_FSX)-> , INPUT | MODE0*/ + 0x198 0x20 /* mcasp0_axr0 (AUD_DIN)<-, INPUT | MODE0*/ + 0x19c 0x22 /* mcasp0_ahclkr-_axr2 (AUD_DOUT)->, | MODE2*/ + >; + }; + + spi1_pins: pinmux_spi1_pins { + pinctrl-single,pins = < + 0x168 0x14 /* MOSI1 OUTPUT_PULLUP | MODE0 */ + 0x16c 0x34 /* MISO1 INPUT_PULLUP | MODE0 */ + 0x108 0x12 /* SCK1 OUTPUT_PULLUP | MODE0 */ + 0x164 0x12 /* SS1 OUTPUT_PULLUP | MODE0 */ + >; + }; + + uart0_pins: pinmux_uart0_pins { + pinctrl-single,pins = < + 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + 0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ + >; + }; + + uart1_pins: pinmux_uart1_pins { + pinctrl-single,pins = < + 0x180 0x30 /* UART1_rxd PULL_UP | MODE0 */ + 0x184 0x00 /* UART1_txd MODE0 */ + >; + }; + + uart2_pins: pinmux_uart2_pins { + pinctrl-single,pins = < + 0x12c 0x31 /* UART2_rxd PULL_UP | MODE1 */ + 0x130 0x01 /* UART2_txd MODE1 */ + >; + }; + + uart4_pins: pinmux_uart4_pins { + pinctrl-single,pins = < + 0x70 0x36 /* UART4_rxd PULL_UP | MODE6 */ + 0x74 0x06 /* UART4_txd MODE6 */ + >; + }; +}; + +&dcan0 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&can_bus_pins>; +}; + +&i2c0 { + status = "okay"; + clock-frequency = <400000>; + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + + tps: tps@24 { + reg = <0x24>; + }; + + rtc@6f { + compatible = "microchip,mcp7941x"; + reg = <0x6f>; + }; + + tlv320aic3x: tlv320aic3x@18 { + compatible = "ti,tlv320aic3x"; + reg = <0x18>; + status = "okay"; + }; +}; + +&i2c1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + + clock-frequency = <100000>; +}; + +&i2c2 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + + clock-frequency = <100000>; +}; + +&epwmss1 { + status = "okay"; +}; + +&ehrpwm1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&tre_ehrpwm1_pins>; +}; + +&epwmss2 { + status = "okay"; +}; + +&ehrpwm2 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&tre_ehrpwm2_pins>; +}; + +&lcdc { + status = "okay"; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + + status = "okay"; +}; + +&uart2 { + pinctrl-names = "default"; + pinctrl-0 = <&uart2_pins>; + + status = "okay"; +}; + +&uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&uart4_pins>; + + status = "okay"; +}; + +&usb { + status = "okay"; + + control@44e10620 { + status = "okay"; + }; + + usb-phy@47401300 { + status = "okay"; + }; + + usb-phy@47401b00 { + status = "okay"; + }; + + usb@47401000 { + status = "okay"; + dr_mode = "peripheral"; + }; + + usb@47401800 { + status = "okay"; + dr_mode = "host"; + }; + + dma-controller@47402000 { + status = "okay"; + }; +}; + +&tps { + compatible = "ti,tps65217"; + ti,pmic-shutdown-controller; + + interrupt-parent = <&intc>; + interrupts = <7>; /* NNMI */ + + regulators { + #address-cells = <1>; + #size-cells = <0>; + + dcdc1_reg: regulator@0 { + reg = <0>; + regulator-name = "vdd_ddr"; + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc2_reg: regulator@1 { + reg = <1>; + /* VDD_MPU voltage limits 0.95V - 1.26V with +/-4% tolerance */ + regulator-name = "vdd_mpu"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1325000>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc3_reg: regulator@2 { + reg = <2>; + /* VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */ + regulator-name = "vdd_core"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1150000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo1_reg: regulator@3 { + reg = <3>; + regulator-always-on; + }; + + ldo2_reg: regulator@4 { + reg = <4>; + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + reg = <5>; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo4_reg: regulator@6 { + reg = <6>; + regulator-always-on; + }; + + rtc@44e3e000 { + ti,system-power-controller; + }; + }; +}; + +&mcasp0 { + pinctrl-names = "default"; + pinctrl-0 = <&tre_audio_pins>; + + status = "okay"; + + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + num-serializer = <16>; + serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ + 2 0 1 0 + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + >; + tx-num-evt = <1>; + rx-num-evt = <1>; +}; + +&mac { + slaves = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&emac_rmii1_pins>; + status = "okay"; +}; + +&davinci_mdio { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&davinci_mdio_default>; + pinctrl-1 = <&davinci_mdio_sleep>; + status = "okay"; +}; + +&cpsw_emac0 { + phy_id = <&davinci_mdio>, <0>; + phy-mode = "rmii"; +}; + +&phy_sel { + rmii-clock-ext; +}; + +&mmc1 { + status = "okay"; + bus-width = <0x4>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&mmc1_pins_default>; + pinctrl-1 = <&mmc1_pins_sleep>; + cd-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + cd-inverted; + vmmc-supply = <&vmmcsd_fixed>; +}; + +&rtc { + system-power-controller; +}; + +&sham { + status = "okay"; +}; + +&aes { + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-bone-argus.dtsi b/arch/arm/boot/dts/am335x-bone-argus.dtsi new file mode 100644 index 0000000..21afad3 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-argus.dtsi @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include + +/ { + ocp { + P8_07_pinmux { + /* gpio2[2] */ + status = "disabled"; + }; + P8_08_pinmux { + /* gpio2[3] */ + status = "disabled"; + }; + P8_09_pinmux { + /* gpio2[5] */ + status = "disabled"; + }; + P8_10_pinmux { + /* gpio2[4] */ + status = "disabled"; + }; + P9_11_pinmux { + /* gpio0[30] */ + status = "disabled"; + }; + P9_17_pinmux { + /* gpio0[5] */ + status = "disabled"; + }; + P9_18_pinmux { + /* gpio0[4] */ + status = "disabled"; + }; + P9_41_pinmux { + /* gpio0[20] */ + status = "disabled"; + }; + P9_42_pinmux { + /* gpio0[7] */ + status = "disabled"; + }; + }; +}; + +/ { + argus-ups { + compatible = "argus-ups"; + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&argus_ups_pins>; /* Refer to previous label */ + /* This section communicates the gpio numbers to the driver module */ + /* Note that gpio controllers appear to be numbered from 1-n here rather than 0-(n-1)????? */ + gpios = <&gpio0 30 0>, /* Request */ + <&gpio0 5 0>, /* Acknowledge */ + <&gpio0 4 0>, /* Watchdog */ + <&gpio2 2 0>, /* LED 1 Green */ + <&gpio2 3 0>, /* LED 1 Red */ + <&gpio2 5 0>, /* LED 2 Green */ + <&gpio2 4 0>, /* LED 2 Red */ + <&gpio0 20 0>, /* General Output #1 */ + <&gpio0 7 0>; /* General Output #2 */ + debug = <1>; + shutdown = <1>; + }; +}; + +&am33xx_pinmux { + argus_ups_pins: pinmux_argus_ups_pins { /* Set up pinmux */ + pinctrl-single,pins = < + 0x070 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_wait0.gpio0_30 */ + 0x15c (PIN_OUTPUT_PULLUP | MUX_MODE7) /* spi0_cs0.gpio0_5 */ + 0x158 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* spi0_d1.gpio0_4 */ + 0x090 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_advn_ale.gpio_2 */ + 0x094 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_oen_ren.gpio2_3 */ + 0x09c (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_ben0_cle.gpio2_5 */ + 0x098 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_gpmc_wen.gpio2_4 */ + 0x1b4 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* xdma_event_intr1.gpio0_20 */ + 0x164 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* ecap0_in_pwm0_out.gpio0_7 */ + >; + }; + + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + BONE_P9_20 0x73 /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) uart1_ctsn.i2c2_sda */ + BONE_P9_19 0x73 /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) uart1_rtsn.i2c2_scl */ + >; + }; +}; + +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + + status = "okay"; + clock-frequency = <100000>; + + rtc@68 { + compatible = "maxim,ds1307"; + reg = <0x68>; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bone-cape-bone-argus.dts b/arch/arm/boot/dts/am335x-bone-cape-bone-argus.dts new file mode 100644 index 0000000..82218f5 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-cape-bone-argus.dts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am335x-bone-common-no-capemgr.dtsi" + +/ { + model = "TI AM335x BeagleBone"; + compatible = "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&ldo3_reg>; +}; + +#include "am335x-bone-argus.dtsi" diff --git b/arch/arm/boot/dts/am335x-bone-common-no-capemgr.dtsi b/arch/arm/boot/dts/am335x-bone-common-no-capemgr.dtsi new file mode 100644 index 0000000..1ba0eb4 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-common-no-capemgr.dtsi @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/ { + cpus { + cpu@0 { + cpu0-supply = <&dcdc2_reg>; + }; + }; + + memory { + device_type = "memory"; + reg = <0x80000000 0x10000000>; /* 256 MB */ + }; + + leds { + pinctrl-names = "default"; + pinctrl-0 = <&user_leds_s0>; + + compatible = "gpio-leds"; + + led@2 { + label = "beaglebone:green:usr0"; + gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + default-state = "off"; + }; + + led@3 { + label = "beaglebone:green:usr1"; + gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + + led@4 { + label = "beaglebone:green:usr2"; + gpios = <&gpio1 23 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "cpu0"; + default-state = "off"; + }; + + led@5 { + label = "beaglebone:green:usr3"; + gpios = <&gpio1 24 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc1"; + default-state = "off"; + }; + }; + + vmmcsd_fixed: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "vmmcsd_fixed"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; +}; + +&am33xx_pinmux { + user_leds_s0: user_leds_s0 { + pinctrl-single,pins = < + AM33XX_IOPAD(0x854, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a5.gpio1_21 */ + AM33XX_IOPAD(0x858, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a6.gpio1_22 */ + AM33XX_IOPAD(0x85c, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a7.gpio1_23 */ + AM33XX_IOPAD(0x860, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a8.gpio1_24 */ + >; + }; + + i2c0_pins: pinmux_i2c0_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x988, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda.i2c0_sda */ + AM33XX_IOPAD(0x98c, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl.i2c0_scl */ + >; + }; + + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x978, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ + AM33XX_IOPAD(0x97c, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ + >; + }; + + uart0_pins: pinmux_uart0_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x970, PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + AM33XX_IOPAD(0x974, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ + >; + }; + + cpsw_default: cpsw_default { + pinctrl-single,pins = < + /* Slave 1 */ + 0x108 (PIN_INPUT | MUX_MODE0) /* mii1_col.mii1_col */ + 0x10c (PIN_INPUT | MUX_MODE0) /* mii1_crs.mii1_crs */ + AM33XX_IOPAD(0x910, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ + AM33XX_IOPAD(0x914, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ + AM33XX_IOPAD(0x918, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ + AM33XX_IOPAD(0x91c, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ + AM33XX_IOPAD(0x920, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ + AM33XX_IOPAD(0x924, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ + AM33XX_IOPAD(0x928, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ + AM33XX_IOPAD(0x930, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ + AM33XX_IOPAD(0x934, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ + AM33XX_IOPAD(0x938, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ + AM33XX_IOPAD(0x940, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ + >; + }; + + cpsw_sleep: cpsw_sleep { + pinctrl-single,pins = < + /* Slave 1 reset value */ + 0x108 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x910, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x914, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x918, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x91c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x920, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x924, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x928, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x930, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x934, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x938, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x940, PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + davinci_mdio_default: davinci_mdio_default { + pinctrl-single,pins = < + /* MDIO */ + AM33XX_IOPAD(0x948, PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + AM33XX_IOPAD(0x94c, PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + >; + }; + + davinci_mdio_sleep: davinci_mdio_sleep { + pinctrl-single,pins = < + /* MDIO reset value */ + AM33XX_IOPAD(0x948, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x94c, PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + mmc1_pins: pinmux_mmc1_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x960, PIN_INPUT | MUX_MODE7) /* GPIO0_6 */ + >; + }; + + emmc_pins: pinmux_emmc_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x880, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ + AM33XX_IOPAD(0x884, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ + AM33XX_IOPAD(0x800, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ + AM33XX_IOPAD(0x804, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ + AM33XX_IOPAD(0x808, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ + AM33XX_IOPAD(0x80c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ + AM33XX_IOPAD(0x810, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad4.mmc1_dat4 */ + AM33XX_IOPAD(0x814, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad5.mmc1_dat5 */ + AM33XX_IOPAD(0x818, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad6.mmc1_dat6 */ + AM33XX_IOPAD(0x81c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad7.mmc1_dat7 */ + >; + }; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + + status = "okay"; +}; + +&usb { + status = "okay"; +}; + +&usb_ctrl_mod { + status = "okay"; +}; + +&usb0_phy { + status = "okay"; +}; + +&usb1_phy { + status = "okay"; +}; + +&usb0 { + status = "okay"; + dr_mode = "peripheral"; +}; + +&usb1 { + status = "okay"; + dr_mode = "host"; +}; + +&cppi41dma { + status = "okay"; +}; + +&i2c0 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + + status = "okay"; + clock-frequency = <400000>; + + tps: tps@24 { + reg = <0x24>; + }; + + baseboard_eeprom: baseboard_eeprom@50 { + compatible = "at,24c256"; + reg = <0x50>; + + #address-cells = <1>; + #size-cells = <1>; + baseboard_data: baseboard_data@0 { + reg = <0 0x100>; + }; + }; +}; + +/include/ "tps65217.dtsi" + +&tps { + /* + * Configure pmic to enter OFF-state instead of SLEEP-state ("RTC-only + * mode") at poweroff. Most BeagleBone versions do not support RTC-only + * mode and risk hardware damage if this mode is entered. + * + * For details, see linux-omap mailing list May 2015 thread + * [PATCH] ARM: dts: am335x-bone* enable pmic-shutdown-controller + * In particular, messages: + * http://www.spinics.net/lists/linux-omap/msg118585.html + * http://www.spinics.net/lists/linux-omap/msg118615.html + * + * You can override this later with + * &tps { /delete-property/ ti,pmic-shutdown-controller; } + * if you want to use RTC-only mode and made sure you are not affected + * by the hardware problems. (Tip: double-check by performing a current + * measurement after shutdown: it should be less than 1 mA.) + */ + ti,pmic-shutdown-controller; + + interrupt-parent = <&intc>; + interrupts = <7>; /* NNMI */ + + regulators { + dcdc1_reg: regulator@0 { + regulator-name = "vdds_dpr"; + regulator-always-on; + }; + + dcdc2_reg: regulator@1 { + /* VDD_MPU voltage limits 0.95V - 1.26V with +/-4% tolerance */ + regulator-name = "vdd_mpu"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1325000>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc3_reg: regulator@2 { + /* VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */ + regulator-name = "vdd_core"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1150000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo1_reg: regulator@3 { + regulator-name = "vio,vrtc,vdds"; + regulator-always-on; + }; + + ldo2_reg: regulator@4 { + regulator-name = "vdd_3v3aux"; + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + regulator-name = "vdd_1v8"; + regulator-always-on; + }; + + ldo4_reg: regulator@6 { + regulator-name = "vdd_3v3a"; + regulator-always-on; + }; + }; +}; + +&cpsw_emac0 { + phy_id = <&davinci_mdio>, <0>; + phy-mode = "mii"; +}; + +&mac { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&cpsw_default>; + pinctrl-1 = <&cpsw_sleep>; + slaves = <1>; + status = "okay"; +}; + +&davinci_mdio { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&davinci_mdio_default>; + pinctrl-1 = <&davinci_mdio_sleep>; + status = "okay"; +}; + +&mmc1 { + status = "okay"; + bus-width = <0x4>; + pinctrl-names = "default"; + pinctrl-0 = <&mmc1_pins>; + cd-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; +}; + +&aes { + status = "okay"; +}; + +&sham { + status = "okay"; +}; + +&wkup_m3_ipc { + ti,scale-data-fw = "am335x-bone-scale-data.bin"; +}; + +&rtc { + system-power-controller; +}; diff --git b/arch/arm/boot/dts/am335x-bone-common-universal.dtsi b/arch/arm/boot/dts/am335x-bone-common-universal.dtsi new file mode 100644 index 0000000..781e33f --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-common-universal.dtsi @@ -0,0 +1,2052 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&am33xx_pinmux { + /************************/ + /* P8 Header */ + /************************/ + + /* P8_01 GND */ + /* P8_02 GND */ + /* P8_03 (ZCZ ball R9 ) emmc */ + /* P8_04 (ZCZ ball T9 ) emmc */ + /* P8_05 (ZCZ ball R8 ) emmc */ + /* P8_06 (ZCZ ball T8 ) emmc */ + + /* P8_07 (ZCZ ball R7 ) */ + P8_07_default_pin: pinmux_P8_07_default_pin { + pinctrl-single,pins = <0x090 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_07_gpio_pin: pinmux_P8_07_gpio_pin { + pinctrl-single,pins = <0x090 0x2F>; }; /* Mode 7, RxActive */ + P8_07_gpio_pu_pin: pinmux_P8_07_gpio_pu_pin { + pinctrl-single,pins = <0x090 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_07_gpio_pd_pin: pinmux_P8_07_gpio_pd_pin { + pinctrl-single,pins = <0x090 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_07_timer_pin: pinmux_P8_07_timer_pin { + pinctrl-single,pins = <0x090 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + + /* P8_08 (ZCZ ball T7 ) */ + P8_08_default_pin: pinmux_P8_08_default_pin { + pinctrl-single,pins = <0x094 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_08_gpio_pin: pinmux_P8_08_gpio_pin { + pinctrl-single,pins = <0x094 0x2F>; }; /* Mode 7, RxActive */ + P8_08_gpio_pu_pin: pinmux_P8_08_gpio_pu_pin { + pinctrl-single,pins = <0x094 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_08_gpio_pd_pin: pinmux_P8_08_gpio_pd_pin { + pinctrl-single,pins = <0x094 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_08_timer_pin: pinmux_P8_08_timer_pin { + pinctrl-single,pins = <0x094 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + + /* P8_09 (ZCZ ball T6 ) */ + P8_09_default_pin: pinmux_P8_09_default_pin { + pinctrl-single,pins = <0x09c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_09_gpio_pin: pinmux_P8_09_gpio_pin { + pinctrl-single,pins = <0x09c 0x2F>; }; /* Mode 7, RxActive */ + P8_09_gpio_pu_pin: pinmux_P8_09_gpio_pu_pin { + pinctrl-single,pins = <0x09c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_09_gpio_pd_pin: pinmux_P8_09_gpio_pd_pin { + pinctrl-single,pins = <0x09c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_09_timer_pin: pinmux_P8_09_timer_pin { + pinctrl-single,pins = <0x09c 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + + /* P8_10 (ZCZ ball U6 ) */ + P8_10_default_pin: pinmux_P8_10_default_pin { + pinctrl-single,pins = <0x098 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_10_gpio_pin: pinmux_P8_10_gpio_pin { + pinctrl-single,pins = <0x098 0x2F>; }; /* Mode 7, RxActive */ + P8_10_gpio_pu_pin: pinmux_P8_10_gpio_pu_pin { + pinctrl-single,pins = <0x098 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_10_gpio_pd_pin: pinmux_P8_10_gpio_pd_pin { + pinctrl-single,pins = <0x098 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_10_timer_pin: pinmux_P8_10_timer_pin { + pinctrl-single,pins = <0x098 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + + /* P8_11 (ZCZ ball R12) */ + P8_11_default_pin: pinmux_P8_11_default_pin { + pinctrl-single,pins = <0x034 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_11_gpio_pin: pinmux_P8_11_gpio_pin { + pinctrl-single,pins = <0x034 0x2F>; }; /* Mode 7, RxActive */ + P8_11_gpio_pu_pin: pinmux_P8_11_gpio_pu_pin { + pinctrl-single,pins = <0x034 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_11_gpio_pd_pin: pinmux_P8_11_gpio_pd_pin { + pinctrl-single,pins = <0x034 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_11_pruout_pin: pinmux_P8_11_pruout_pin { + pinctrl-single,pins = <0x034 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_11_qep_pin: pinmux_P8_11_qep_pin { + pinctrl-single,pins = <0x034 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_12 (ZCZ ball T12) */ + P8_12_default_pin: pinmux_P8_12_default_pin { + pinctrl-single,pins = <0x030 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_12_gpio_pin: pinmux_P8_12_gpio_pin { + pinctrl-single,pins = <0x030 0x2F>; }; /* Mode 7, RxActive */ + P8_12_gpio_pu_pin: pinmux_P8_12_gpio_pu_pin { + pinctrl-single,pins = <0x030 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_12_gpio_pd_pin: pinmux_P8_12_gpio_pd_pin { + pinctrl-single,pins = <0x030 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_12_pruout_pin: pinmux_P8_12_pruout_pin { + pinctrl-single,pins = <0x030 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_12_qep_pin: pinmux_P8_12_qep_pin { + pinctrl-single,pins = <0x030 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_13 (ZCZ ball T10) */ + P8_13_default_pin: pinmux_P8_13_default_pin { + pinctrl-single,pins = <0x024 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_13_gpio_pin: pinmux_P8_13_gpio_pin { + pinctrl-single,pins = <0x024 0x2F>; }; /* Mode 7, RxActive */ + P8_13_gpio_pu_pin: pinmux_P8_13_gpio_pu_pin { + pinctrl-single,pins = <0x024 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_13_gpio_pd_pin: pinmux_P8_13_gpio_pd_pin { + pinctrl-single,pins = <0x024 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_13_pwm_pin: pinmux_P8_13_pwm_pin { + pinctrl-single,pins = <0x024 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_14 (ZCZ ball T11) */ + P8_14_default_pin: pinmux_P8_14_default_pin { + pinctrl-single,pins = <0x028 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_14_gpio_pin: pinmux_P8_14_gpio_pin { + pinctrl-single,pins = <0x028 0x2F>; }; /* Mode 7, RxActive */ + P8_14_gpio_pu_pin: pinmux_P8_14_gpio_pu_pin { + pinctrl-single,pins = <0x028 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_14_gpio_pd_pin: pinmux_P8_14_gpio_pd_pin { + pinctrl-single,pins = <0x028 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_14_pwm_pin: pinmux_P8_14_pwm_pin { + pinctrl-single,pins = <0x028 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_15 (ZCZ ball U13) */ + P8_15_default_pin: pinmux_P8_15_default_pin { + pinctrl-single,pins = <0x03c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_15_gpio_pin: pinmux_P8_15_gpio_pin { + pinctrl-single,pins = <0x03c 0x2F>; }; /* Mode 7, RxActive */ + P8_15_gpio_pu_pin: pinmux_P8_15_gpio_pu_pin { + pinctrl-single,pins = <0x03c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_15_gpio_pd_pin: pinmux_P8_15_gpio_pd_pin { + pinctrl-single,pins = <0x03c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_15_pruin_pin: pinmux_P8_15_pruin_pin { + pinctrl-single,pins = <0x03c 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_15_qep_pin: pinmux_P8_15_qep_pin { + pinctrl-single,pins = <0x03c 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_16 (ZCZ ball V13) */ + P8_16_default_pin: pinmux_P8_16_default_pin { + pinctrl-single,pins = <0x038 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_16_gpio_pin: pinmux_P8_16_gpio_pin { + pinctrl-single,pins = <0x038 0x2F>; }; /* Mode 7, RxActive */ + P8_16_gpio_pu_pin: pinmux_P8_16_gpio_pu_pin { + pinctrl-single,pins = <0x038 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_16_gpio_pd_pin: pinmux_P8_16_gpio_pd_pin { + pinctrl-single,pins = <0x038 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_16_pruin_pin: pinmux_P8_16_pruin_pin { + pinctrl-single,pins = <0x038 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_16_qep_pin: pinmux_P8_16_qep_pin { + pinctrl-single,pins = <0x038 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_17 (ZCZ ball U12) */ + P8_17_default_pin: pinmux_P8_17_default_pin { + pinctrl-single,pins = <0x02c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_17_gpio_pin: pinmux_P8_17_gpio_pin { + pinctrl-single,pins = <0x02c 0x2F>; }; /* Mode 7, RxActive */ + P8_17_gpio_pu_pin: pinmux_P8_17_gpio_pu_pin { + pinctrl-single,pins = <0x02c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_17_gpio_pd_pin: pinmux_P8_17_gpio_pd_pin { + pinctrl-single,pins = <0x02c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_17_pwm_pin: pinmux_P8_17_pwm_pin { + pinctrl-single,pins = <0x02c 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_18 (ZCZ ball V12) */ + P8_18_default_pin: pinmux_P8_18_default_pin { + pinctrl-single,pins = <0x08c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_18_gpio_pin: pinmux_P8_18_gpio_pin { + pinctrl-single,pins = <0x08c 0x2F>; }; /* Mode 7, RxActive */ + P8_18_gpio_pu_pin: pinmux_P8_18_gpio_pu_pin { + pinctrl-single,pins = <0x08c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_18_gpio_pd_pin: pinmux_P8_18_gpio_pd_pin { + pinctrl-single,pins = <0x08c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + + /* P8_19 (ZCZ ball U10) */ + P8_19_default_pin: pinmux_P8_19_default_pin { + pinctrl-single,pins = <0x020 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_19_gpio_pin: pinmux_P8_19_gpio_pin { + pinctrl-single,pins = <0x020 0x2F>; }; /* Mode 7, RxActive */ + P8_19_gpio_pu_pin: pinmux_P8_19_gpio_pu_pin { + pinctrl-single,pins = <0x020 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_19_gpio_pd_pin: pinmux_P8_19_gpio_pd_pin { + pinctrl-single,pins = <0x020 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_19_pwm_pin: pinmux_P8_19_pwm_pin { + pinctrl-single,pins = <0x020 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P8_20 (ZCZ ball V9 ) emmc */ + /* P8_21 (ZCZ ball U9 ) emmc */ + /* P8_22 (ZCZ ball V8 ) emmc */ + /* P8_23 (ZCZ ball U8 ) emmc */ + /* P8_24 (ZCZ ball V7 ) emmc */ + /* P8_25 (ZCZ ball U7 ) emmc */ + + /* P8_26 (ZCZ ball V6 ) */ + P8_26_default_pin: pinmux_P8_26_default_pin { + pinctrl-single,pins = <0x07c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_26_gpio_pin: pinmux_P8_26_gpio_pin { + pinctrl-single,pins = <0x07c 0x2F>; }; /* Mode 7, RxActive */ + P8_26_gpio_pu_pin: pinmux_P8_26_gpio_pu_pin { + pinctrl-single,pins = <0x07c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_26_gpio_pd_pin: pinmux_P8_26_gpio_pd_pin { + pinctrl-single,pins = <0x07c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + + /* P8_27 (ZCZ ball U5 ) hdmi */ + P8_27_default_pin: pinmux_P8_27_default_pin { + pinctrl-single,pins = <0x0e0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_27_gpio_pin: pinmux_P8_27_gpio_pin { + pinctrl-single,pins = <0x0e0 0x2F>; }; /* Mode 7, RxActive */ + P8_27_gpio_pu_pin: pinmux_P8_27_gpio_pu_pin { + pinctrl-single,pins = <0x0e0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_27_gpio_pd_pin: pinmux_P8_27_gpio_pd_pin { + pinctrl-single,pins = <0x0e0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_27_pruout_pin: pinmux_P8_27_pruout_pin { + pinctrl-single,pins = <0x0e0 0x05>; }; /* Mode 5, Pull-Down*/ + P8_27_pruin_pin: pinmux_P8_27_pruin_pin { + pinctrl-single,pins = <0x0e0 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_27_hdmi_pin: pinmux_P8_27_hdmi_pin { + pinctrl-single,pins = <0x0e0 0x00>; }; /* lcd_vsync.lcd_vsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + + /* P8_28 (ZCZ ball V5 ) hdmi */ + P8_28_default_pin: pinmux_P8_28_default_pin { + pinctrl-single,pins = <0x0e8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_28_gpio_pin: pinmux_P8_28_gpio_pin { + pinctrl-single,pins = <0x0e8 0x2F>; }; /* Mode 7, RxActive */ + P8_28_gpio_pu_pin: pinmux_P8_28_gpio_pu_pin { + pinctrl-single,pins = <0x0e8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_28_gpio_pd_pin: pinmux_P8_28_gpio_pd_pin { + pinctrl-single,pins = <0x0e8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_28_pruout_pin: pinmux_P8_28_pruout_pin { + pinctrl-single,pins = <0x0e8 0x05>; }; /* Mode 5, Pull-Down */ + P8_28_pruin_pin: pinmux_P8_28_pruin_pin { + pinctrl-single,pins = <0x0e8 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_28_hdmi_pin: pinmux_P8_28_hdmi_pin { + pinctrl-single,pins = <0x0e8 0x00>; }; /* lcd_pclk.lcd_pclk, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + + /* P8_29 (ZCZ ball R5 ) hdmi */ + P8_29_default_pin: pinmux_P8_29_default_pin { + pinctrl-single,pins = <0x0e4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_29_gpio_pin: pinmux_P8_29_gpio_pin { + pinctrl-single,pins = <0x0e4 0x2F>; }; /* Mode 7, RxActive */ + P8_29_gpio_pu_pin: pinmux_P8_29_gpio_pu_pin { + pinctrl-single,pins = <0x0e4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_29_gpio_pd_pin: pinmux_P8_29_gpio_pd_pin { + pinctrl-single,pins = <0x0e4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_29_pruout_pin: pinmux_P8_29_pruout_pin { + pinctrl-single,pins = <0x0e4 0x05>; }; /* Mode 5, Pull-Down*/ + P8_29_pruin_pin: pinmux_P8_29_pruin_pin { + pinctrl-single,pins = <0x0e4 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_29_hdmi_pin: pinmux_P8_29_hdmi_pin { + pinctrl-single,pins = <0x0e4 0x00>; }; /* lcd_hsync.lcd_hsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + + /* P8_30 (ZCZ ball R6 ) hdmi */ + P8_30_default_pin: pinmux_P8_30_default_pin { + pinctrl-single,pins = <0x0ec 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_30_gpio_pin: pinmux_P8_30_gpio_pin { + pinctrl-single,pins = <0x0ec 0x2F>; }; /* Mode 7, RxActive */ + P8_30_gpio_pu_pin: pinmux_P8_30_gpio_pu_pin { + pinctrl-single,pins = <0x0ec 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_30_gpio_pd_pin: pinmux_P8_30_gpio_pd_pin { + pinctrl-single,pins = <0x0ec 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_30_pruout_pin: pinmux_P8_30_pruout_pin { + pinctrl-single,pins = <0x0ec 0x05>; }; /* Mode 5, Pull-Down*/ + P8_30_pruin_pin: pinmux_P8_30_pruin_pin { + pinctrl-single,pins = <0x0ec 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_30_hdmi_pin: pinmux_P8_30_hdmi_pin { + pinctrl-single,pins = <0x0ec 0x00>; }; /* lcd_ac_bias_en.lcd_ac_bias_en, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + + /* P8_31 (ZCZ ball V4 ) hdmi */ + P8_31_default_pin: pinmux_P8_31_default_pin { + pinctrl-single,pins = <0x0d8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_31_gpio_pin: pinmux_P8_31_gpio_pin { + pinctrl-single,pins = <0x0d8 0x2F>; }; /* Mode 7, RxActive */ + P8_31_gpio_pu_pin: pinmux_P8_31_gpio_pu_pin { + pinctrl-single,pins = <0x0d8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_31_gpio_pd_pin: pinmux_P8_31_gpio_pd_pin { + pinctrl-single,pins = <0x0d8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_31_uart_pin: pinmux_P8_31_uart_pin { + pinctrl-single,pins = <0x0d8 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + P8_31_hdmi_pin: pinmux_P8_31_hdmi_pin { + pinctrl-single,pins = <0x0d8 0x08>; }; /* lcd_data14.lcd_data14, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_32 (ZCZ ball T5 ) hdmi */ + P8_32_default_pin: pinmux_P8_32_default_pin { + pinctrl-single,pins = <0x0dc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_32_gpio_pin: pinmux_P8_32_gpio_pin { + pinctrl-single,pins = <0x0dc 0x2F>; }; /* Mode 7, RxActive */ + P8_32_gpio_pu_pin: pinmux_P8_32_gpio_pu_pin { + pinctrl-single,pins = <0x0dc 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_32_gpio_pd_pin: pinmux_P8_32_gpio_pd_pin { + pinctrl-single,pins = <0x0dc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_32_uart_pin: pinmux_P8_32_uart_pin { + pinctrl-single,pins = <0x0dc 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_32_hdmi_pin: pinmux_P8_32_hdmi_pin { + pinctrl-single,pins = <0x0dc 0x08>; }; /* lcd_data15.lcd_data15, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_33 (ZCZ ball V3 ) hdmi */ + P8_33_default_pin: pinmux_P8_33_default_pin { + pinctrl-single,pins = <0x0d4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_33_gpio_pin: pinmux_P8_33_gpio_pin { + pinctrl-single,pins = <0x0d4 0x2F>; }; /* Mode 7, RxActive */ + P8_33_gpio_pu_pin: pinmux_P8_33_gpio_pu_pin { + pinctrl-single,pins = <0x0d4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_33_gpio_pd_pin: pinmux_P8_33_gpio_pd_pin { + pinctrl-single,pins = <0x0d4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_33_hdmi_pin: pinmux_P8_33_hdmi_pin { + pinctrl-single,pins = <0x0d4 0x08>; }; /* lcd_data13.lcd_data13, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_34 (ZCZ ball U4 ) hdmi */ + P8_34_default_pin: pinmux_P8_34_default_pin { + pinctrl-single,pins = <0x0cc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_34_gpio_pin: pinmux_P8_34_gpio_pin { + pinctrl-single,pins = <0x0cc 0x2F>; }; /* Mode 7, RxActive */ + P8_34_gpio_pu_pin: pinmux_P8_34_gpio_pu_pin { + pinctrl-single,pins = <0x0cc 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_34_gpio_pd_pin: pinmux_P8_34_gpio_pd_pin { + pinctrl-single,pins = <0x0cc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_34_pwm_pin: pinmux_P8_34_pwm_pin { + pinctrl-single,pins = <0x0cc 0x22>; }; /* Mode 2, Pull-Down, RxActive */ + P8_34_hdmi_pin: pinmux_P8_34_hdmi_pin { + pinctrl-single,pins = <0x0cc 0x08>; }; /* lcd_data11.lcd_data11, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_35 (ZCZ ball V2 ) hdmi */ + P8_35_default_pin: pinmux_P8_35_default_pin { + pinctrl-single,pins = <0x0d0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_35_gpio_pin: pinmux_P8_35_gpio_pin { + pinctrl-single,pins = <0x0d0 0x2F>; }; /* Mode 7, RxActive */ + P8_35_gpio_pu_pin: pinmux_P8_35_gpio_pu_pin { + pinctrl-single,pins = <0x0d0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_35_gpio_pd_pin: pinmux_P8_35_gpio_pd_pin { + pinctrl-single,pins = <0x0d0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_35_hdmi_pin: pinmux_P8_35_hdmi_pin { + pinctrl-single,pins = <0x0d0 0x08>; }; /* lcd_data12.lcd_data12, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_36 (ZCZ ball U3 ) hdmi */ + P8_36_default_pin: pinmux_P8_36_default_pin { + pinctrl-single,pins = <0x0c8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_36_gpio_pin: pinmux_P8_36_gpio_pin { + pinctrl-single,pins = <0x0c8 0x2F>; }; /* Mode 7, RxActive */ + P8_36_gpio_pu_pin: pinmux_P8_36_gpio_pu_pin { + pinctrl-single,pins = <0x0c8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_36_gpio_pd_pin: pinmux_P8_36_gpio_pd_pin { + pinctrl-single,pins = <0x0c8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_36_pwm_pin: pinmux_P8_36_pwm_pin { + pinctrl-single,pins = <0x0c8 0x22>; }; /* Mode 2, Pull-Down, RxActive */ + P8_36_hdmi_pin: pinmux_P8_36_hdmi_pin { + pinctrl-single,pins = <0x0c8 0x08>; }; /* lcd_data10.lcd_data10, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_37 (ZCZ ball U1 ) hdmi */ + P8_37_default_pin: pinmux_P8_37_default_pin { + pinctrl-single,pins = <0x0c0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_37_gpio_pin: pinmux_P8_37_gpio_pin { + pinctrl-single,pins = <0x0c0 0x2F>; }; /* Mode 7, RxActive */ + P8_37_gpio_pu_pin: pinmux_P8_37_gpio_pu_pin { + pinctrl-single,pins = <0x0c0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_37_gpio_pd_pin: pinmux_P8_37_gpio_pd_pin { + pinctrl-single,pins = <0x0c0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_37_uart_pin: pinmux_P8_37_uart_pin { + pinctrl-single,pins = <0x0c0 0x04>; }; /* Mode 4, Pull-Down*/ + P8_37_pwm_pin: pinmux_P8_37_pwm_pin { + pinctrl-single,pins = <0x0c0 0x02>; }; /* Mode 2, Pull-Down*/ + P8_37_hdmi_pin: pinmux_P8_37_hdmi_pin { + pinctrl-single,pins = <0x0c0 0x08>; }; /* lcd_data8.lcd_data8, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + + /* P8_38 (ZCZ ball U2 ) hdmi */ + P8_38_default_pin: pinmux_P8_38_default_pin { + pinctrl-single,pins = <0x0c4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_38_gpio_pin: pinmux_P8_38_gpio_pin { + pinctrl-single,pins = <0x0c4 0x2F>; }; /* Mode 7, RxActive */ + P8_38_gpio_pu_pin: pinmux_P8_38_gpio_pu_pin { + pinctrl-single,pins = <0x0c4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_38_gpio_pd_pin: pinmux_P8_38_gpio_pd_pin { + pinctrl-single,pins = <0x0c4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_38_uart_pin: pinmux_P8_38_uart_pin { + pinctrl-single,pins = <0x0c4 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + P8_38_pwm_pin: pinmux_P8_38_pwm_pin { + pinctrl-single,pins = <0x0c4 0x22>; }; /* Mode 2, Pull-Down, RxActive */ + P8_38_hdmi_pin: pinmux_P8_38_hdmi_pin { + pinctrl-single,pins = <0x0c4 0x08>; }; /* lcd_data9.lcd_data9, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + + /* P8_39 (ZCZ ball T3 ) hdmi */ + P8_39_default_pin: pinmux_P8_39_default_pin { + pinctrl-single,pins = <0x0b8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_39_gpio_pin: pinmux_P8_39_gpio_pin { + pinctrl-single,pins = <0x0b8 0x2F>; }; /* Mode 7, RxActive */ + P8_39_gpio_pu_pin: pinmux_P8_39_gpio_pu_pin { + pinctrl-single,pins = <0x0b8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_39_gpio_pd_pin: pinmux_P8_39_gpio_pd_pin { + pinctrl-single,pins = <0x0b8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_39_pruout_pin: pinmux_P8_39_pruout_pin { + pinctrl-single,pins = <0x0b8 0x05>; }; /* Mode 5, Pull-Down*/ + P8_39_pruin_pin: pinmux_P8_39_pruin_pin { + pinctrl-single,pins = <0x0b8 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_39_hdmi_pin: pinmux_P8_39_hdmi_pin { + pinctrl-single,pins = <0x0b8 0x08>; }; /* lcd_data6.lcd_data6, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_40 (ZCZ ball T4 ) hdmi */ + P8_40_default_pin: pinmux_P8_40_default_pin { + pinctrl-single,pins = <0x0bc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_40_gpio_pin: pinmux_P8_40_gpio_pin { + pinctrl-single,pins = <0x0bc 0x2F>; }; /* Mode 7, RxActive */ + P8_40_gpio_pu_pin: pinmux_P8_40_gpio_pu_pin { + pinctrl-single,pins = <0x0bc 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_40_gpio_pd_pin: pinmux_P8_40_gpio_pd_pin { + pinctrl-single,pins = <0x0bc 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_40_pruout_pin: pinmux_P8_40_pruout_pin { + pinctrl-single,pins = <0x0bc 0x05>; }; /* Mode 5, Pull-Down*/ + P8_40_pruin_pin: pinmux_P8_40_pruin_pin { + pinctrl-single,pins = <0x0bc 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_40_hdmi_pin: pinmux_P8_40_hdmi_pin { + pinctrl-single,pins = <0x0bc 0x08>; }; /* lcd_data7.lcd_data7, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_41 (ZCZ ball T1 ) hdmi */ + P8_41_default_pin: pinmux_P8_41_default_pin { + pinctrl-single,pins = <0x0b0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_41_gpio_pin: pinmux_P8_41_gpio_pin { + pinctrl-single,pins = <0x0b0 0x2F>; }; /* Mode 7, RxActive */ + P8_41_gpio_pu_pin: pinmux_P8_41_gpio_pu_pin { + pinctrl-single,pins = <0x0b0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_41_gpio_pd_pin: pinmux_P8_41_gpio_pd_pin { + pinctrl-single,pins = <0x0b0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_41_pruout_pin: pinmux_P8_41_pruout_pin { + pinctrl-single,pins = <0x0b0 0x05>; }; /* Mode 5, Pull-Down*/ + P8_41_pruin_pin: pinmux_P8_41_pruin_pin { + pinctrl-single,pins = <0x0b0 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_41_hdmi_pin: pinmux_P8_41_hdmi_pin { + pinctrl-single,pins = <0x0b0 0x08>; }; /* lcd_data4.lcd_data4, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_42 (ZCZ ball T2 ) hdmi */ + P8_42_default_pin: pinmux_P8_42_default_pin { + pinctrl-single,pins = <0x0b4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_42_gpio_pin: pinmux_P8_42_gpio_pin { + pinctrl-single,pins = <0x0b4 0x2F>; }; /* Mode 7, RxActive */ + P8_42_gpio_pu_pin: pinmux_P8_42_gpio_pu_pin { + pinctrl-single,pins = <0x0b4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_42_gpio_pd_pin: pinmux_P8_42_gpio_pd_pin { + pinctrl-single,pins = <0x0b4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_42_pruout_pin: pinmux_P8_42_pruout_pin { + pinctrl-single,pins = <0x0b4 0x05>; }; /* Mode 5, Pull-Down*/ + P8_42_pruin_pin: pinmux_P8_42_pruin_pin { + pinctrl-single,pins = <0x0b4 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_42_hdmi_pin: pinmux_P8_42_hdmi_pin { + pinctrl-single,pins = <0x0b4 0x08>; }; /* lcd_data5.lcd_data5, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_43 (ZCZ ball R3 ) hdmi */ + P8_43_default_pin: pinmux_P8_43_default_pin { + pinctrl-single,pins = <0x0a8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_43_gpio_pin: pinmux_P8_43_gpio_pin { + pinctrl-single,pins = <0x0a8 0x2F>; }; /* Mode 7, RxActive */ + P8_43_gpio_pu_pin: pinmux_P8_43_gpio_pu_pin { + pinctrl-single,pins = <0x0a8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_43_gpio_pd_pin: pinmux_P8_43_gpio_pd_pin { + pinctrl-single,pins = <0x0a8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_43_pruout_pin: pinmux_P8_43_pruout_pin { + pinctrl-single,pins = <0x0a8 0x05>; }; /* Mode 5, Pull-Down*/ + P8_43_pruin_pin: pinmux_P8_43_pruin_pin { + pinctrl-single,pins = <0x0a8 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_43_pwm_pin: pinmux_P8_43_pwm_pin { + pinctrl-single,pins = <0x0a8 0x03>; }; /* Mode 3, Pull-Down */ + P8_43_hdmi_pin: pinmux_P8_43_hdmi_pin { + pinctrl-single,pins = <0x0a8 0x08>; }; /* lcd_data2.lcd_data2, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_44 (ZCZ ball R4 ) hdmi */ + P8_44_default_pin: pinmux_P8_44_default_pin { + pinctrl-single,pins = <0x0ac 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_44_gpio_pin: pinmux_P8_44_gpio_pin { + pinctrl-single,pins = <0x0ac 0x2F>; }; /* Mode 7, RxActive */ + P8_44_gpio_pu_pin: pinmux_P8_44_gpio_pu_pin { + pinctrl-single,pins = <0x0ac 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_44_gpio_pd_pin: pinmux_P8_44_gpio_pd_pin { + pinctrl-single,pins = <0x0ac 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_44_pruout_pin: pinmux_P8_44_pruout_pin { + pinctrl-single,pins = <0x0ac 0x05>; }; /* Mode 5, Pull-Down*/ + P8_44_pruin_pin: pinmux_P8_44_pruin_pin { + pinctrl-single,pins = <0x0ac 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_44_pwm_pin: pinmux_P8_44_pwm_pin { + pinctrl-single,pins = <0x0ac 0x23>; }; /* Mode 3, Pull-Down, RxActive */ + P8_44_hdmi_pin: pinmux_P8_44_hdmi_pin { + pinctrl-single,pins = <0x0ac 0x08>; }; /* lcd_data3.lcd_data3, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_45 (ZCZ ball R1 ) hdmi */ + P8_45_default_pin: pinmux_P8_45_default_pin { + pinctrl-single,pins = <0x0a0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_45_gpio_pin: pinmux_P8_45_gpio_pin { + pinctrl-single,pins = <0x0a0 0x2F>; }; /* Mode 7, RxActive */ + P8_45_gpio_pu_pin: pinmux_P8_45_gpio_pu_pin { + pinctrl-single,pins = <0x0a0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_45_gpio_pd_pin: pinmux_P8_45_gpio_pd_pin { + pinctrl-single,pins = <0x0a0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_45_pruout_pin: pinmux_P8_45_pruout_pin { + pinctrl-single,pins = <0x0a0 0x05>; }; /* Mode 5, Pull-Down*/ + P8_45_pruin_pin: pinmux_P8_45_pruin_pin { + pinctrl-single,pins = <0x0a0 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_45_pwm_pin: pinmux_P8_45_pwm_pin { + pinctrl-single,pins = <0x0a0 0x03>; }; /* Mode 3, Pull-Down*/ + P8_45_hdmi_pin: pinmux_P8_45_hdmi_pin { + pinctrl-single,pins = <0x0a0 0x08>; }; /* lcd_data0.lcd_data0, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /* P8_46 (ZCZ ball R2 ) hdmi */ + P8_46_default_pin: pinmux_P8_46_default_pin { + pinctrl-single,pins = <0x0a4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_46_gpio_pin: pinmux_P8_46_gpio_pin { + pinctrl-single,pins = <0x0a4 0x2F>; }; /* Mode 7, RxActive */ + P8_46_gpio_pu_pin: pinmux_P8_46_gpio_pu_pin { + pinctrl-single,pins = <0x0a4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P8_46_gpio_pd_pin: pinmux_P8_46_gpio_pd_pin { + pinctrl-single,pins = <0x0a4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P8_46_pruout_pin: pinmux_P8_46_pruout_pin { + pinctrl-single,pins = <0x0a4 0x05>; }; /* Mode 5, Pull-Down*/ + P8_46_pruin_pin: pinmux_P8_46_pruin_pin { + pinctrl-single,pins = <0x0a4 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P8_46_pwm_pin: pinmux_P8_46_pwm_pin { + pinctrl-single,pins = <0x0a4 0x03>; }; /* Mode 3, Pull-Down*/ + P8_46_hdmi_pin: pinmux_P8_46_hdmi_pin { + pinctrl-single,pins = <0x0a4 0x08>; }; /* lcd_data1.lcd_data1, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + + /************************/ + /* P9 Header */ + /************************/ + + /* P9_01 GND */ + /* P9_02 GND */ + /* P9_03 3.3V */ + /* P9_04 3.3V */ + /* P9_05 VDD_5V */ + /* P9_06 VDD_5V */ + /* P9_07 SYS_5V */ + /* P9_08 SYS_5V */ + /* P9_09 PWR_BUT */ + /* P9_10 (ZCZ ball A10) RESETn */ + + /* P9_11 (ZCZ ball T17) */ + P9_11_default_pin: pinmux_P9_11_default_pin { + pinctrl-single,pins = <0x070 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_11_gpio_pin: pinmux_P9_11_gpio_pin { + pinctrl-single,pins = <0x070 0x2F>; }; /* Mode 7, RxActive */ + P9_11_gpio_pu_pin: pinmux_P9_11_gpio_pu_pin { + pinctrl-single,pins = <0x070 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_11_gpio_pd_pin: pinmux_P9_11_gpio_pd_pin { + pinctrl-single,pins = <0x070 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_11_uart_pin: pinmux_P9_11_uart_pin { + pinctrl-single,pins = <0x070 0x36>; }; /* Mode 6, Pull-Up, RxActive */ + + /* P9_12 (ZCZ ball U18) */ + P9_12_default_pin: pinmux_P9_12_default_pin { + pinctrl-single,pins = <0x078 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_12_gpio_pin: pinmux_P9_12_gpio_pin { + pinctrl-single,pins = <0x078 0x2F>; }; /* Mode 7, RxActive */ + P9_12_gpio_pu_pin: pinmux_P9_12_gpio_pu_pin { + pinctrl-single,pins = <0x078 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_12_gpio_pd_pin: pinmux_P9_12_gpio_pd_pin { + pinctrl-single,pins = <0x078 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + + /* P9_13 (ZCZ ball U17) */ + P9_13_default_pin: pinmux_P9_13_default_pin { + pinctrl-single,pins = <0x074 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_13_gpio_pin: pinmux_P9_13_gpio_pin { + pinctrl-single,pins = <0x074 0x2F>; }; /* Mode 7, RxActive */ + P9_13_gpio_pu_pin: pinmux_P9_13_gpio_pu_pin { + pinctrl-single,pins = <0x074 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_13_gpio_pd_pin: pinmux_P9_13_gpio_pd_pin { + pinctrl-single,pins = <0x074 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_13_uart_pin: pinmux_P9_13_uart_pin { + pinctrl-single,pins = <0x074 0x36>; }; /* Mode 6, Pull-Up, RxActive */ + + /* P9_14 (ZCZ ball U14) */ + P9_14_default_pin: pinmux_P9_14_default_pin { + pinctrl-single,pins = <0x048 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_14_gpio_pin: pinmux_P9_14_gpio_pin { + pinctrl-single,pins = <0x048 0x2F>; }; /* Mode 7, RxActive */ + P9_14_gpio_pu_pin: pinmux_P9_14_gpio_pu_pin { + pinctrl-single,pins = <0x048 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_14_gpio_pd_pin: pinmux_P9_14_gpio_pd_pin { + pinctrl-single,pins = <0x048 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_14_pwm_pin: pinmux_P9_14_pwm_pin { + pinctrl-single,pins = <0x048 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_15 (ZCZ ball R13) */ + P9_15_default_pin: pinmux_P9_15_default_pin { + pinctrl-single,pins = <0x040 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_15_gpio_pin: pinmux_P9_15_gpio_pin { + pinctrl-single,pins = <0x040 0x2F>; }; /* Mode 7, RxActive */ + P9_15_gpio_pu_pin: pinmux_P9_15_gpio_pu_pin { + pinctrl-single,pins = <0x040 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_15_gpio_pd_pin: pinmux_P9_15_gpio_pd_pin { + pinctrl-single,pins = <0x040 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_15_pwm_pin: pinmux_P9_15_pwm_pin { + pinctrl-single,pins = <0x040 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_16 (ZCZ ball T14) */ + P9_16_default_pin: pinmux_P9_16_default_pin { + pinctrl-single,pins = <0x04c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_16_gpio_pin: pinmux_P9_16_gpio_pin { + pinctrl-single,pins = <0x04c 0x2F>; }; /* Mode 7, RxActive */ + P9_16_gpio_pu_pin: pinmux_P9_16_gpio_pu_pin { + pinctrl-single,pins = <0x04c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_16_gpio_pd_pin: pinmux_P9_16_gpio_pd_pin { + pinctrl-single,pins = <0x04c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_16_pwm_pin: pinmux_P9_16_pwm_pin { + pinctrl-single,pins = <0x04c 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_17 (ZCZ ball A16) */ + P9_17_default_pin: pinmux_P9_17_default_pin { + pinctrl-single,pins = <0x15c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_17_gpio_pin: pinmux_P9_17_gpio_pin { + pinctrl-single,pins = <0x15c 0x2F>; }; /* Mode 7, RxActive */ + P9_17_gpio_pu_pin: pinmux_P9_17_gpio_pu_pin { + pinctrl-single,pins = <0x15c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_17_gpio_pd_pin: pinmux_P9_17_gpio_pd_pin { + pinctrl-single,pins = <0x15c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_17_spi_pin: pinmux_P9_17_spi_pin { + pinctrl-single,pins = <0x15c 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_17_i2c_pin: pinmux_P9_17_i2c_pin { + pinctrl-single,pins = <0x15c 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_17_pwm_pin: pinmux_P9_17_pwm_pin { + pinctrl-single,pins = <0x15c 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + + /* P9_18 (ZCZ ball B16) */ + P9_18_default_pin: pinmux_P9_18_default_pin { + pinctrl-single,pins = <0x158 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_18_gpio_pin: pinmux_P9_18_gpio_pin { + pinctrl-single,pins = <0x158 0x2F>; }; /* Mode 7, RxActive */ + P9_18_gpio_pu_pin: pinmux_P9_18_gpio_pu_pin { + pinctrl-single,pins = <0x158 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_18_gpio_pd_pin: pinmux_P9_18_gpio_pd_pin { + pinctrl-single,pins = <0x158 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_18_spi_pin: pinmux_P9_18_spi_pin { + pinctrl-single,pins = <0x158 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_18_i2c_pin: pinmux_P9_18_i2c_pin { + pinctrl-single,pins = <0x158 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_18_pwm_pin: pinmux_P9_18_pwm_pin { + pinctrl-single,pins = <0x158 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + + /* P9_19 (ZCZ ball D17) */ + P9_19_default_pin: pinmux_P9_19_default_pin { + pinctrl-single,pins = <0x17c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_19_gpio_pin: pinmux_P9_19_gpio_pin { + pinctrl-single,pins = <0x17c 0x2F>; }; /* Mode 7, RxActive */ + P9_19_gpio_pu_pin: pinmux_P9_19_gpio_pu_pin { + pinctrl-single,pins = <0x17c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_19_gpio_pd_pin: pinmux_P9_19_gpio_pd_pin { + pinctrl-single,pins = <0x17c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_19_can_pin: pinmux_P9_19_can_pin { + pinctrl-single,pins = <0x17c 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_19_i2c_pin: pinmux_P9_19_i2c_pin { + pinctrl-single,pins = <0x17c 0x73>; }; /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) */ + + /* P9_20 (ZCZ ball D18) */ + P9_20_default_pin: pinmux_P9_20_default_pin { + pinctrl-single,pins = <0x178 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_20_gpio_pin: pinmux_P9_20_gpio_pin { + pinctrl-single,pins = <0x178 0x2F>; }; /* Mode 7, RxActive */ + P9_20_gpio_pu_pin: pinmux_P9_20_gpio_pu_pin { + pinctrl-single,pins = <0x178 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_20_gpio_pd_pin: pinmux_P9_20_gpio_pd_pin { + pinctrl-single,pins = <0x178 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_20_can_pin: pinmux_P9_20_can_pin { + pinctrl-single,pins = <0x178 0x12>; }; /* Mode 2, Pull-Up, RxActive */ + P9_20_i2c_pin: pinmux_P9_20_i2c_pin { + pinctrl-single,pins = <0x178 0x73>; }; /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) */ + + /* P9_21 (ZCZ ball B17) */ + P9_21_default_pin: pinmux_P9_21_default_pin { + pinctrl-single,pins = <0x154 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_21_gpio_pin: pinmux_P9_21_gpio_pin { + pinctrl-single,pins = <0x154 0x2F>; }; /* Mode 7, RxActive */ + P9_21_gpio_pu_pin: pinmux_P9_21_gpio_pu_pin { + pinctrl-single,pins = <0x154 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_21_gpio_pd_pin: pinmux_P9_21_gpio_pd_pin { + pinctrl-single,pins = <0x154 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_21_spi_pin: pinmux_P9_21_spi_pin { + pinctrl-single,pins = <0x154 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_21_uart_pin: pinmux_P9_21_uart_pin { + pinctrl-single,pins = <0x154 0x31>; }; /* Mode 1, Pull-Up, RxActive */ + P9_21_i2c_pin: pinmux_P9_21_i2c_pin { + pinctrl-single,pins = <0x154 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_21_pwm_pin: pinmux_P9_21_pwm_pin { + pinctrl-single,pins = <0x154 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + + /* P9_22 (ZCZ ball A17) */ + P9_22_default_pin: pinmux_P9_22_default_pin { + pinctrl-single,pins = <0x150 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_22_gpio_pin: pinmux_P9_22_gpio_pin { + pinctrl-single,pins = <0x150 0x2F>; }; /* Mode 7, RxActive */ + P9_22_gpio_pu_pin: pinmux_P9_22_gpio_pu_pin { + pinctrl-single,pins = <0x150 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_22_gpio_pd_pin: pinmux_P9_22_gpio_pd_pin { + pinctrl-single,pins = <0x150 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_22_spi_pin: pinmux_P9_22_spi_pin { + pinctrl-single,pins = <0x150 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_22_uart_pin: pinmux_P9_22_uart_pin { + pinctrl-single,pins = <0x150 0x31>; }; /* Mode 1, Pull-Up, RxActive */ + P9_22_i2c_pin: pinmux_P9_22_i2c_pin { + pinctrl-single,pins = <0x150 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_22_pwm_pin: pinmux_P9_22_pwm_pin { + pinctrl-single,pins = <0x150 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + + /* P9_23 (ZCZ ball V14) */ + P9_23_default_pin: pinmux_P9_23_default_pin { + pinctrl-single,pins = <0x044 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_23_gpio_pin: pinmux_P9_23_gpio_pin { + pinctrl-single,pins = <0x044 0x2F>; }; /* Mode 7, RxActive */ + P9_23_gpio_pu_pin: pinmux_P9_23_gpio_pu_pin { + pinctrl-single,pins = <0x044 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_23_gpio_pd_pin: pinmux_P9_23_gpio_pd_pin { + pinctrl-single,pins = <0x044 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_23_pwm_pin: pinmux_P9_23_pwm_pin { + pinctrl-single,pins = <0x044 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_24 (ZCZ ball D15) */ + P9_24_default_pin: pinmux_P9_24_default_pin { + pinctrl-single,pins = <0x184 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_24_gpio_pin: pinmux_P9_24_gpio_pin { + pinctrl-single,pins = <0x184 0x2F>; }; /* Mode 7, RxActive */ + P9_24_gpio_pu_pin: pinmux_P9_24_gpio_pu_pin { + pinctrl-single,pins = <0x184 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_24_gpio_pd_pin: pinmux_P9_24_gpio_pd_pin { + pinctrl-single,pins = <0x184 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_24_uart_pin: pinmux_P9_24_uart_pin { + pinctrl-single,pins = <0x184 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_24_can_pin: pinmux_P9_24_can_pin { + pinctrl-single,pins = <0x184 0x32>; }; /* Mode 2, Pull-Up, RxActive */ + P9_24_i2c_pin: pinmux_P9_24_i2c_pin { + pinctrl-single,pins = <0x184 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + P9_24_pruin_pin: pinmux_P9_24_pruin_pin { + pinctrl-single,pins = <0x184 0x36>; }; /* Mode 6, Pull-Up, RxActive */ + + /* P9_25 (ZCZ ball A14) Audio */ + P9_25_default_pin: pinmux_P9_25_default_pin { + pinctrl-single,pins = <0x1ac 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_25_gpio_pin: pinmux_P9_25_gpio_pin { + pinctrl-single,pins = <0x1ac 0x2F>; }; /* Mode 7, RxActive */ + P9_25_gpio_pu_pin: pinmux_P9_25_gpio_pu_pin { + pinctrl-single,pins = <0x1ac 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_25_gpio_pd_pin: pinmux_P9_25_gpio_pd_pin { + pinctrl-single,pins = <0x1ac 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_25_qep_pin: pinmux_P9_25_qep_pin { + pinctrl-single,pins = <0x1ac 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_25_pruout_pin: pinmux_P9_25_pruout_pin { + pinctrl-single,pins = <0x1ac 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_25_pruin_pin: pinmux_P9_25_pruin_pin { + pinctrl-single,pins = <0x1ac 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P9_25_audio_pin: pinmux_P9_25_audio_pin { + pinctrl-single,pins = <0x1ac (PIN_INPUT_PULLUP | MUX_MODE0)>; }; /* mcasp0_ahclkx.mcasp0_ahclkx */ + + /* P9_26 (ZCZ ball D16) */ + P9_26_default_pin: pinmux_P9_26_default_pin { + pinctrl-single,pins = <0x180 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_26_gpio_pin: pinmux_P9_26_gpio_pin { + pinctrl-single,pins = <0x180 0x2F>; }; /* Mode 7, RxActive */ + P9_26_gpio_pu_pin: pinmux_P9_26_gpio_pu_pin { + pinctrl-single,pins = <0x180 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_26_gpio_pd_pin: pinmux_P9_26_gpio_pd_pin { + pinctrl-single,pins = <0x180 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_26_uart_pin: pinmux_P9_26_uart_pin { + pinctrl-single,pins = <0x180 0x30>; }; /* Mode 0, Pull-Up, RxActive */ + P9_26_can_pin: pinmux_P9_26_can_pin { + pinctrl-single,pins = <0x180 0x12>; }; /* Mode 2, Pull-Up, RxActive */ + P9_26_i2c_pin: pinmux_P9_26_i2c_pin { + pinctrl-single,pins = <0x180 0x33>; }; /* Mode 3, Pull-Up, RxActive */ + P9_26_pruin_pin: pinmux_P9_26_pruin_pin { + pinctrl-single,pins = <0x180 0x36>; }; /* Mode 6, Pull-Up, RxActive */ + + /* P9_27 (ZCZ ball C13) */ + P9_27_default_pin: pinmux_P9_27_default_pin { + pinctrl-single,pins = <0x1a4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_27_gpio_pin: pinmux_P9_27_gpio_pin { + pinctrl-single,pins = <0x1a4 0x2F>; }; /* Mode 7, RxActive */ + P9_27_gpio_pu_pin: pinmux_P9_27_gpio_pu_pin { + pinctrl-single,pins = <0x1a4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_27_gpio_pd_pin: pinmux_P9_27_gpio_pd_pin { + pinctrl-single,pins = <0x1a4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_27_qep_pin: pinmux_P9_27_qep_pin { + pinctrl-single,pins = <0x1a4 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_27_pruout_pin: pinmux_P9_27_pruout_pin { + pinctrl-single,pins = <0x1a4 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_27_pruin_pin: pinmux_P9_27_pruin_pin { + pinctrl-single,pins = <0x1a4 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_28 (ZCZ ball C12) Audio */ + P9_28_default_pin: pinmux_P9_28_default_pin { + pinctrl-single,pins = <0x19c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_28_gpio_pin: pinmux_P9_28_gpio_pin { + pinctrl-single,pins = <0x19c 0x2F>; }; /* Mode 7, RxActive */ + P9_28_gpio_pu_pin: pinmux_P9_28_gpio_pu_pin { + pinctrl-single,pins = <0x19c 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_28_gpio_pd_pin: pinmux_P9_28_gpio_pd_pin { + pinctrl-single,pins = <0x19c 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_28_pwm_pin: pinmux_P9_28_pwm_pin { + pinctrl-single,pins = <0x19c 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_28_spi_pin: pinmux_P9_28_spi_pin { + pinctrl-single,pins = <0x19c 0x23>; }; /* Mode 3, Pull-Down, RxActive */ + P9_28_pwm2_pin: pinmux_P9_28_pwm2_pin { + pinctrl-single,pins = <0x19c 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + P9_28_pruout_pin: pinmux_P9_28_pruout_pin { + pinctrl-single,pins = <0x19c 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_28_pruin_pin: pinmux_P9_28_pruin_pin { + pinctrl-single,pins = <0x19c 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P9_28_audio_pin: pinmux_P9_28_audio_pin { + pinctrl-single,pins = <0x19c (PIN_OUTPUT_PULLDOWN | MUX_MODE2)>; }; /* mcasp0_ahclkr.mcasp0_axr2 */ + + /* P9_29 (ZCZ ball B13) Audio */ + P9_29_default_pin: pinmux_P9_29_default_pin { + pinctrl-single,pins = <0x194 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_29_gpio_pin: pinmux_P9_29_gpio_pin { + pinctrl-single,pins = <0x194 0x2F>; }; /* Mode 7, RxActive */ + P9_29_gpio_pu_pin: pinmux_P9_29_gpio_pu_pin { + pinctrl-single,pins = <0x194 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_29_gpio_pd_pin: pinmux_P9_29_gpio_pd_pin { + pinctrl-single,pins = <0x194 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_29_pwm_pin: pinmux_P9_29_pwm_pin { + pinctrl-single,pins = <0x194 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_29_spi_pin: pinmux_P9_29_spi_pin { + pinctrl-single,pins = <0x194 0x23>; }; /* Mode 3, Pull-Down, RxActive */ + P9_29_pruout_pin: pinmux_P9_29_pruout_pin { + pinctrl-single,pins = <0x194 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_29_pruin_pin: pinmux_P9_29_pruin_pin { + pinctrl-single,pins = <0x194 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P9_29_audio_pin: pinmux_P9_29_audio_pin { + pinctrl-single,pins = <0x194 (PIN_OUTPUT_PULLUP | MUX_MODE0)>; }; /* mcasp0_fsx.mcasp0_fsx */ + + /* P9_30 (ZCZ ball D12) */ + P9_30_default_pin: pinmux_P9_30_default_pin { + pinctrl-single,pins = <0x198 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_30_gpio_pin: pinmux_P9_30_gpio_pin { + pinctrl-single,pins = <0x198 0x2F>; }; /* Mode 7, RxActive */ + P9_30_gpio_pu_pin: pinmux_P9_30_gpio_pu_pin { + pinctrl-single,pins = <0x198 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_30_gpio_pd_pin: pinmux_P9_30_gpio_pd_pin { + pinctrl-single,pins = <0x198 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_30_pwm_pin: pinmux_P9_30_pwm_pin { + pinctrl-single,pins = <0x198 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_30_spi_pin: pinmux_P9_30_spi_pin { + pinctrl-single,pins = <0x198 0x23>; }; /* Mode 3, Pull-Down, RxActive */ + P9_30_pruout_pin: pinmux_P9_30_pruout_pin { + pinctrl-single,pins = <0x198 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_30_pruin_pin: pinmux_P9_30_pruin_pin { + pinctrl-single,pins = <0x198 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_31 (ZCZ ball A13) Audio */ + P9_31_default_pin: pinmux_P9_31_default_pin { + pinctrl-single,pins = <0x190 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_31_gpio_pin: pinmux_P9_31_gpio_pin { + pinctrl-single,pins = <0x190 0x2F>; }; /* Mode 7, RxActive */ + P9_31_gpio_pu_pin: pinmux_P9_31_gpio_pu_pin { + pinctrl-single,pins = <0x190 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_31_gpio_pd_pin: pinmux_P9_31_gpio_pd_pin { + pinctrl-single,pins = <0x190 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_31_pwm_pin: pinmux_P9_31_pwm_pin { + pinctrl-single,pins = <0x190 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_31_spi_pin: pinmux_P9_31_spi_pin { + pinctrl-single,pins = <0x190 0x23>; }; /* Mode 3, Pull-Down, RxActive */ + P9_31_pruout_pin: pinmux_P9_31_pruout_pin { + pinctrl-single,pins = <0x190 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_31_pruin_pin: pinmux_P9_31_pruin_pin { + pinctrl-single,pins = <0x190 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + P9_31_audio_pin: pinmux_P9_31_audio_pin { + pinctrl-single,pins = <0x190 (PIN_OUTPUT_PULLDOWN | MUX_MODE0)>; }; /* mcasp0_aclkx.mcasp0_aclkx */ + + /* P9_32 VADC */ + /* P9_33 (ZCZ ball C8 ) AIN4 */ + /* P9_34 AGND */ + /* P9_35 (ZCZ ball A8 ) AIN6 */ + /* P9_36 (ZCZ ball B8 ) AIN5 */ + /* P9_37 (ZCZ ball B7 ) AIN2 */ + /* P9_38 (ZCZ ball A7 ) AIN3 */ + /* P9_39 (ZCZ ball B6 ) AIN0 */ + /* P9_40 (ZCZ ball C7 ) AIN1 */ + + /* P9_41 (ZCZ ball D14) */ + P9_41_default_pin: pinmux_P9_41_default_pin { + pinctrl-single,pins = <0x1b4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_41_gpio_pin: pinmux_P9_41_gpio_pin { + pinctrl-single,pins = <0x1b4 0x2F>; }; /* Mode 7, RxActive */ + P9_41_gpio_pu_pin: pinmux_P9_41_gpio_pu_pin { + pinctrl-single,pins = <0x1b4 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_41_gpio_pd_pin: pinmux_P9_41_gpio_pd_pin { + pinctrl-single,pins = <0x1b4 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_41_timer_pin: pinmux_P9_41_timer_pin { + pinctrl-single,pins = <0x1b4 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + P9_41_pruin_pin: pinmux_P9_41_pruin_pin { + pinctrl-single,pins = <0x1b4 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + + /* P9_41.1 */ + /* P9_91 (ZCZ ball D13) */ + P9_91_default_pin: pinmux_P9_91_default_pin { + pinctrl-single,pins = <0x1a8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_91_gpio_pin: pinmux_P9_91_gpio_pin { + pinctrl-single,pins = <0x1a8 0x2F>; }; /* Mode 7, RxActive */ + P9_91_gpio_pu_pin: pinmux_P9_91_gpio_pu_pin { + pinctrl-single,pins = <0x1a8 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_91_gpio_pd_pin: pinmux_P9_91_gpio_pd_pin { + pinctrl-single,pins = <0x1a8 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_91_qep_pin: pinmux_P9_91_qep_pin { + pinctrl-single,pins = <0x1a8 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_91_pruout_pin: pinmux_P9_91_pruout_pin { + pinctrl-single,pins = <0x1a8 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_91_pruin_pin: pinmux_P9_91_pruin_pin { + pinctrl-single,pins = <0x1a8 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_42 (ZCZ ball C18) */ + P9_42_default_pin: pinmux_P9_42_default_pin { + pinctrl-single,pins = <0x164 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_42_gpio_pin: pinmux_P9_42_gpio_pin { + pinctrl-single,pins = <0x164 0x2F>; }; /* Mode 7, RxActive */ + P9_42_gpio_pu_pin: pinmux_P9_42_gpio_pu_pin { + pinctrl-single,pins = <0x164 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_42_gpio_pd_pin: pinmux_P9_42_gpio_pd_pin { + pinctrl-single,pins = <0x164 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_42_pwm_pin: pinmux_P9_42_pwm_pin { + pinctrl-single,pins = <0x164 0x20>; }; /* Mode 0, Pull-Down, RxActive */ + P9_42_uart_pin: pinmux_P9_42_uart_pin { + pinctrl-single,pins = <0x164 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_42_spics_pin: pinmux_P9_42_spics_pin { + pinctrl-single,pins = <0x164 0x22>; }; /* Mode 2, Pull-Down, RxActive */ + P9_42_spiclk_pin: pinmux_P9_42_spiclk_pin { + pinctrl-single,pins = <0x164 0x24>; }; /* Mode 4, Pull-Down, RxActive */ + + /* P9_42.1 */ + /* P9_92 (ZCZ ball B12) */ + P9_92_default_pin: pinmux_P9_92_default_pin { + pinctrl-single,pins = <0x1a0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_92_gpio_pin: pinmux_P9_92_gpio_pin { + pinctrl-single,pins = <0x1a0 0x2F>; }; /* Mode 7, RxActive */ + P9_92_gpio_pu_pin: pinmux_P9_92_gpio_pu_pin { + pinctrl-single,pins = <0x1a0 0x37>; }; /* Mode 7, Pull-Up, RxActive */ + P9_92_gpio_pd_pin: pinmux_P9_92_gpio_pd_pin { + pinctrl-single,pins = <0x1a0 0x27>; }; /* Mode 7, Pull-Down, RxActive */ + P9_92_qep_pin: pinmux_P9_92_qep_pin { + pinctrl-single,pins = <0x1a0 0x21>; }; /* Mode 1, Pull-Down, RxActive */ + P9_92_pruout_pin: pinmux_P9_92_pruout_pin { + pinctrl-single,pins = <0x1a0 0x25>; }; /* Mode 5, Pull-Down, RxActive */ + P9_92_pruin_pin: pinmux_P9_92_pruin_pin { + pinctrl-single,pins = <0x1a0 0x26>; }; /* Mode 6, Pull-Down, RxActive */ + + /* P9_43 GND */ + /* P9_44 GND */ + /* P9_45 GND */ + /* P9_46 GND */ +}; + +/**********************************************************************/ +/* Pin Multiplex Helpers */ +/* */ +/* These provide userspace runtime pin configuration for the */ +/* BeagleBone cape expansion headers */ +/**********************************************************************/ + +&ocp { + /************************/ + /* P8 Header */ + /************************/ + + P8_07_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "timer"; + pinctrl-0 = <&P8_07_default_pin>; + pinctrl-1 = <&P8_07_gpio_pin>; + pinctrl-2 = <&P8_07_gpio_pu_pin>; + pinctrl-3 = <&P8_07_gpio_pd_pin>; + pinctrl-4 = <&P8_07_timer_pin>; + }; + + P8_08_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "timer"; + pinctrl-0 = <&P8_08_default_pin>; + pinctrl-1 = <&P8_08_gpio_pin>; + pinctrl-2 = <&P8_08_gpio_pu_pin>; + pinctrl-3 = <&P8_08_gpio_pd_pin>; + pinctrl-4 = <&P8_08_timer_pin>; + }; + + P8_09_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "timer"; + pinctrl-0 = <&P8_09_default_pin>; + pinctrl-1 = <&P8_09_gpio_pin>; + pinctrl-2 = <&P8_09_gpio_pu_pin>; + pinctrl-3 = <&P8_09_gpio_pd_pin>; + pinctrl-4 = <&P8_09_timer_pin>; + }; + + P8_10_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "timer"; + pinctrl-0 = <&P8_10_default_pin>; + pinctrl-1 = <&P8_10_gpio_pin>; + pinctrl-2 = <&P8_10_gpio_pu_pin>; + pinctrl-3 = <&P8_10_gpio_pd_pin>; + pinctrl-4 = <&P8_10_timer_pin>; + }; + + P8_11_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "qep"; + pinctrl-0 = <&P8_11_default_pin>; + pinctrl-1 = <&P8_11_gpio_pin>; + pinctrl-2 = <&P8_11_gpio_pu_pin>; + pinctrl-3 = <&P8_11_gpio_pd_pin>; + pinctrl-4 = <&P8_11_pruout_pin>; + pinctrl-5 = <&P8_11_qep_pin>; + }; + + P8_12_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "qep"; + pinctrl-0 = <&P8_12_default_pin>; + pinctrl-1 = <&P8_12_gpio_pin>; + pinctrl-2 = <&P8_12_gpio_pu_pin>; + pinctrl-3 = <&P8_12_gpio_pd_pin>; + pinctrl-4 = <&P8_12_pruout_pin>; + pinctrl-5 = <&P8_12_qep_pin>; + }; + + P8_13_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P8_13_default_pin>; + pinctrl-1 = <&P8_13_gpio_pin>; + pinctrl-2 = <&P8_13_gpio_pu_pin>; + pinctrl-3 = <&P8_13_gpio_pd_pin>; + pinctrl-4 = <&P8_13_pwm_pin>; + }; + + P8_14_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P8_14_default_pin>; + pinctrl-1 = <&P8_14_gpio_pin>; + pinctrl-2 = <&P8_14_gpio_pu_pin>; + pinctrl-3 = <&P8_14_gpio_pd_pin>; + pinctrl-4 = <&P8_14_pwm_pin>; + }; + + P8_15_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruin", "qep"; + pinctrl-0 = <&P8_15_default_pin>; + pinctrl-1 = <&P8_15_gpio_pin>; + pinctrl-2 = <&P8_15_gpio_pu_pin>; + pinctrl-3 = <&P8_15_gpio_pd_pin>; + pinctrl-4 = <&P8_15_pruin_pin>; + pinctrl-5 = <&P8_15_qep_pin>; + }; + + P8_16_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruin", "qep"; + pinctrl-0 = <&P8_16_default_pin>; + pinctrl-1 = <&P8_16_gpio_pin>; + pinctrl-2 = <&P8_16_gpio_pu_pin>; + pinctrl-3 = <&P8_16_gpio_pd_pin>; + pinctrl-4 = <&P8_16_pruin_pin>; + pinctrl-5 = <&P8_16_qep_pin>; + }; + + P8_17_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P8_17_default_pin>; + pinctrl-1 = <&P8_17_gpio_pin>; + pinctrl-2 = <&P8_17_gpio_pu_pin>; + pinctrl-3 = <&P8_17_gpio_pd_pin>; + pinctrl-4 = <&P8_17_pwm_pin>; + }; + + P8_18_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio"; + pinctrl-0 = <&P8_18_default_pin>; + pinctrl-1 = <&P8_18_gpio_pin>; + pinctrl-2 = <&P8_18_gpio_pu_pin>; + pinctrl-3 = <&P8_18_gpio_pd_pin>; + }; + + P8_19_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P8_19_default_pin>; + pinctrl-1 = <&P8_19_gpio_pin>; + pinctrl-2 = <&P8_19_gpio_pu_pin>; + pinctrl-3 = <&P8_19_gpio_pd_pin>; + pinctrl-4 = <&P8_19_pwm_pin>; + }; + + P8_26_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio"; + pinctrl-0 = <&P8_26_default_pin>; + pinctrl-1 = <&P8_26_gpio_pin>; + pinctrl-2 = <&P8_26_gpio_pu_pin>; + pinctrl-3 = <&P8_26_gpio_pd_pin>; + }; + + P8_27_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_27_default_pin>; + pinctrl-1 = <&P8_27_gpio_pin>; + pinctrl-2 = <&P8_27_gpio_pu_pin>; + pinctrl-3 = <&P8_27_gpio_pd_pin>; + pinctrl-4 = <&P8_27_pruout_pin>; + pinctrl-5 = <&P8_27_pruin_pin>; + pinctrl-6 = <&P8_27_hdmi_pin>; + }; + + P8_28_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_28_default_pin>; + pinctrl-1 = <&P8_28_gpio_pin>; + pinctrl-2 = <&P8_28_gpio_pu_pin>; + pinctrl-3 = <&P8_28_gpio_pd_pin>; + pinctrl-4 = <&P8_28_pruout_pin>; + pinctrl-5 = <&P8_28_pruin_pin>; + pinctrl-6 = <&P8_28_hdmi_pin>; + }; + + P8_29_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_29_default_pin>; + pinctrl-1 = <&P8_29_gpio_pin>; + pinctrl-2 = <&P8_29_gpio_pu_pin>; + pinctrl-3 = <&P8_29_gpio_pd_pin>; + pinctrl-4 = <&P8_29_pruout_pin>; + pinctrl-5 = <&P8_29_pruin_pin>; + pinctrl-6 = <&P8_29_hdmi_pin>; + }; + + P8_30_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_30_default_pin>; + pinctrl-1 = <&P8_30_gpio_pin>; + pinctrl-2 = <&P8_30_gpio_pu_pin>; + pinctrl-3 = <&P8_30_gpio_pd_pin>; + pinctrl-4 = <&P8_30_pruout_pin>; + pinctrl-5 = <&P8_30_pruin_pin>; + pinctrl-6 = <&P8_30_hdmi_pin>; + }; + + P8_31_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd","uart", "hdmi"; + pinctrl-0 = <&P8_31_default_pin>; + pinctrl-1 = <&P8_31_gpio_pin>; + pinctrl-2 = <&P8_31_gpio_pu_pin>; + pinctrl-3 = <&P8_31_gpio_pd_pin>; + pinctrl-4 = <&P8_31_uart_pin>; + pinctrl-5 = <&P8_31_hdmi_pin>; + }; + + P8_32_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "hdmi"; + pinctrl-0 = <&P8_32_default_pin>; + pinctrl-1 = <&P8_32_gpio_pin>; + pinctrl-2 = <&P8_32_gpio_pu_pin>; + pinctrl-3 = <&P8_32_gpio_pd_pin>; + pinctrl-4 = <&P8_32_hdmi_pin>; + }; + + P8_33_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "hdmi"; + pinctrl-0 = <&P8_33_default_pin>; + pinctrl-1 = <&P8_33_gpio_pin>; + pinctrl-2 = <&P8_33_gpio_pu_pin>; + pinctrl-3 = <&P8_33_gpio_pd_pin>; + pinctrl-4 = <&P8_33_hdmi_pin>; + }; + + P8_34_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd","pwm", "hdmi"; + pinctrl-0 = <&P8_34_default_pin>; + pinctrl-1 = <&P8_34_gpio_pin>; + pinctrl-2 = <&P8_34_gpio_pu_pin>; + pinctrl-3 = <&P8_34_gpio_pd_pin>; + pinctrl-4 = <&P8_34_pwm_pin>; + pinctrl-5 = <&P8_34_hdmi_pin>; + }; + + P8_35_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "hdmi"; + pinctrl-0 = <&P8_35_default_pin>; + pinctrl-1 = <&P8_35_gpio_pin>; + pinctrl-2 = <&P8_35_gpio_pu_pin>; + pinctrl-3 = <&P8_35_gpio_pd_pin>; + pinctrl-4 = <&P8_35_hdmi_pin>; + }; + + P8_36_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd","pwm", "hdmi"; + pinctrl-0 = <&P8_36_default_pin>; + pinctrl-1 = <&P8_36_gpio_pin>; + pinctrl-2 = <&P8_36_gpio_pu_pin>; + pinctrl-3 = <&P8_36_gpio_pd_pin>; + pinctrl-4 = <&P8_36_pwm_pin>; + pinctrl-5 = <&P8_36_hdmi_pin>; + }; + + P8_37_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd","uart","pwm", "hdmi"; + pinctrl-0 = <&P8_37_default_pin>; + pinctrl-1 = <&P8_37_gpio_pin>; + pinctrl-2 = <&P8_37_gpio_pu_pin>; + pinctrl-3 = <&P8_37_gpio_pd_pin>; + pinctrl-4 = <&P8_37_uart_pin>; + pinctrl-5 = <&P8_37_pwm_pin>; + pinctrl-6 = <&P8_37_hdmi_pin>; + }; + + P8_38_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd","uart","pwm", "hdmi"; + pinctrl-0 = <&P8_38_default_pin>; + pinctrl-1 = <&P8_38_gpio_pin>; + pinctrl-2 = <&P8_38_gpio_pu_pin>; + pinctrl-3 = <&P8_38_gpio_pd_pin>; + pinctrl-4 = <&P8_38_uart_pin>; + pinctrl-5 = <&P8_38_pwm_pin>; + pinctrl-6 = <&P8_38_hdmi_pin>; + }; + + P8_39_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_39_default_pin>; + pinctrl-1 = <&P8_39_gpio_pin>; + pinctrl-2 = <&P8_39_gpio_pu_pin>; + pinctrl-3 = <&P8_39_gpio_pd_pin>; + pinctrl-4 = <&P8_39_pruout_pin>; + pinctrl-5 = <&P8_39_pruin_pin>; + pinctrl-6 = <&P8_39_hdmi_pin>; + }; + + P8_40_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_40_default_pin>; + pinctrl-1 = <&P8_40_gpio_pin>; + pinctrl-2 = <&P8_40_gpio_pu_pin>; + pinctrl-3 = <&P8_40_gpio_pd_pin>; + pinctrl-4 = <&P8_40_pruout_pin>; + pinctrl-5 = <&P8_40_pruin_pin>; + pinctrl-6 = <&P8_40_hdmi_pin>; + }; + + P8_41_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_41_default_pin>; + pinctrl-1 = <&P8_41_gpio_pin>; + pinctrl-2 = <&P8_41_gpio_pu_pin>; + pinctrl-3 = <&P8_41_gpio_pd_pin>; + pinctrl-4 = <&P8_41_pruout_pin>; + pinctrl-5 = <&P8_41_pruin_pin>; + pinctrl-6 = <&P8_41_hdmi_pin>; + }; + + P8_42_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin", "hdmi"; + pinctrl-0 = <&P8_42_default_pin>; + pinctrl-1 = <&P8_42_gpio_pin>; + pinctrl-2 = <&P8_42_gpio_pu_pin>; + pinctrl-3 = <&P8_42_gpio_pd_pin>; + pinctrl-4 = <&P8_42_pruout_pin>; + pinctrl-5 = <&P8_42_pruin_pin>; + pinctrl-6 = <&P8_42_hdmi_pin>; + }; + + P8_43_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin","pwm", "hdmi"; + pinctrl-0 = <&P8_43_default_pin>; + pinctrl-1 = <&P8_43_gpio_pin>; + pinctrl-2 = <&P8_43_gpio_pu_pin>; + pinctrl-3 = <&P8_43_gpio_pd_pin>; + pinctrl-4 = <&P8_43_pruout_pin>; + pinctrl-5 = <&P8_43_pruin_pin>; + pinctrl-6 = <&P8_43_pwm_pin>; + pinctrl-7 = <&P8_43_hdmi_pin>; + }; + + P8_44_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin","pwm", "hdmi"; + pinctrl-0 = <&P8_44_default_pin>; + pinctrl-1 = <&P8_44_gpio_pin>; + pinctrl-2 = <&P8_44_gpio_pu_pin>; + pinctrl-3 = <&P8_44_gpio_pd_pin>; + pinctrl-4 = <&P8_44_pruout_pin>; + pinctrl-5 = <&P8_44_pruin_pin>; + pinctrl-6 = <&P8_44_pwm_pin>; + pinctrl-7 = <&P8_44_hdmi_pin>; + }; + + P8_45_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin","pwm", "hdmi"; + pinctrl-0 = <&P8_45_default_pin>; + pinctrl-1 = <&P8_45_gpio_pin>; + pinctrl-2 = <&P8_45_gpio_pu_pin>; + pinctrl-3 = <&P8_45_gpio_pd_pin>; + pinctrl-4 = <&P8_45_pruout_pin>; + pinctrl-5 = <&P8_45_pruin_pin>; + pinctrl-6 = <&P8_45_pwm_pin>; + pinctrl-7 = <&P8_45_hdmi_pin>; + }; + + P8_46_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pruout", "pruin","pwm", "hdmi"; + pinctrl-0 = <&P8_46_default_pin>; + pinctrl-1 = <&P8_46_gpio_pin>; + pinctrl-2 = <&P8_46_gpio_pu_pin>; + pinctrl-3 = <&P8_46_gpio_pd_pin>; + pinctrl-4 = <&P8_46_pruout_pin>; + pinctrl-5 = <&P8_46_pruin_pin>; + pinctrl-6 = <&P8_46_pwm_pin>; + pinctrl-7 = <&P8_46_hdmi_pin>; + }; + + /************************/ + /* P9 Header */ + /************************/ + + P9_11_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "uart"; + pinctrl-0 = <&P9_11_default_pin>; + pinctrl-1 = <&P9_11_gpio_pin>; + pinctrl-2 = <&P9_11_gpio_pu_pin>; + pinctrl-3 = <&P9_11_gpio_pd_pin>; + pinctrl-4 = <&P9_11_uart_pin>; + }; + + P9_12_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio"; + pinctrl-0 = <&P9_12_default_pin>; + pinctrl-1 = <&P9_12_gpio_pin>; + pinctrl-2 = <&P9_12_gpio_pu_pin>; + pinctrl-3 = <&P9_12_gpio_pd_pin>; + }; + + P9_13_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "uart"; + pinctrl-0 = <&P9_13_default_pin>; + pinctrl-1 = <&P9_13_gpio_pin>; + pinctrl-2 = <&P9_13_gpio_pu_pin>; + pinctrl-3 = <&P9_13_gpio_pd_pin>; + pinctrl-4 = <&P9_13_uart_pin>; + }; + + P9_14_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P9_14_default_pin>; + pinctrl-1 = <&P9_14_gpio_pin>; + pinctrl-2 = <&P9_14_gpio_pu_pin>; + pinctrl-3 = <&P9_14_gpio_pd_pin>; + pinctrl-4 = <&P9_14_pwm_pin>; + }; + + P9_15_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P9_15_default_pin>; + pinctrl-1 = <&P9_15_gpio_pin>; + pinctrl-2 = <&P9_15_gpio_pu_pin>; + pinctrl-3 = <&P9_15_gpio_pd_pin>; + pinctrl-4 = <&P9_15_pwm_pin>; + }; + + P9_16_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P9_16_default_pin>; + pinctrl-1 = <&P9_16_gpio_pin>; + pinctrl-2 = <&P9_16_gpio_pu_pin>; + pinctrl-3 = <&P9_16_gpio_pd_pin>; + pinctrl-4 = <&P9_16_pwm_pin>; + }; + + P9_17_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "spi", "i2c", "pwm"; + pinctrl-0 = <&P9_17_default_pin>; + pinctrl-1 = <&P9_17_gpio_pin>; + pinctrl-2 = <&P9_17_gpio_pu_pin>; + pinctrl-3 = <&P9_17_gpio_pd_pin>; + pinctrl-4 = <&P9_17_spi_pin>; + pinctrl-5 = <&P9_17_i2c_pin>; + pinctrl-6 = <&P9_17_pwm_pin>; + }; + + P9_18_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "spi", "i2c", "pwm"; + pinctrl-0 = <&P9_18_default_pin>; + pinctrl-1 = <&P9_18_gpio_pin>; + pinctrl-2 = <&P9_18_gpio_pu_pin>; + pinctrl-3 = <&P9_18_gpio_pd_pin>; + pinctrl-4 = <&P9_18_spi_pin>; + pinctrl-5 = <&P9_18_i2c_pin>; + pinctrl-6 = <&P9_18_pwm_pin>; + }; + + P9_19_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "can", "i2c"; + pinctrl-0 = <&P9_19_default_pin>; + pinctrl-1 = <&P9_19_gpio_pin>; + pinctrl-2 = <&P9_19_gpio_pu_pin>; + pinctrl-3 = <&P9_19_gpio_pd_pin>; + pinctrl-4 = <&P9_19_can_pin>; + pinctrl-5 = <&P9_19_i2c_pin>; + }; + + P9_20_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "can", "i2c"; + pinctrl-0 = <&P9_20_default_pin>; + pinctrl-1 = <&P9_20_gpio_pin>; + pinctrl-2 = <&P9_20_gpio_pu_pin>; + pinctrl-3 = <&P9_20_gpio_pd_pin>; + pinctrl-4 = <&P9_20_can_pin>; + pinctrl-5 = <&P9_20_i2c_pin>; + }; + + P9_21_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "spi", "uart", "i2c", "pwm"; + pinctrl-0 = <&P9_21_default_pin>; + pinctrl-1 = <&P9_21_gpio_pin>; + pinctrl-2 = <&P9_21_gpio_pu_pin>; + pinctrl-3 = <&P9_21_gpio_pd_pin>; + pinctrl-4 = <&P9_21_spi_pin>; + pinctrl-5 = <&P9_21_uart_pin>; + pinctrl-6 = <&P9_21_i2c_pin>; + pinctrl-7 = <&P9_21_pwm_pin>; + }; + + P9_22_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "spi", "uart", "i2c", "pwm"; + pinctrl-0 = <&P9_22_default_pin>; + pinctrl-1 = <&P9_22_gpio_pin>; + pinctrl-2 = <&P9_22_gpio_pu_pin>; + pinctrl-3 = <&P9_22_gpio_pd_pin>; + pinctrl-4 = <&P9_22_spi_pin>; + pinctrl-5 = <&P9_22_uart_pin>; + pinctrl-6 = <&P9_22_i2c_pin>; + pinctrl-7 = <&P9_22_pwm_pin>; + }; + + P9_23_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm"; + pinctrl-0 = <&P9_23_default_pin>; + pinctrl-1 = <&P9_23_gpio_pin>; + pinctrl-2 = <&P9_23_gpio_pu_pin>; + pinctrl-3 = <&P9_23_gpio_pd_pin>; + pinctrl-4 = <&P9_23_pwm_pin>; + }; + + P9_24_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "uart", "can", "i2c", "pruin"; + pinctrl-0 = <&P9_24_default_pin>; + pinctrl-1 = <&P9_24_gpio_pin>; + pinctrl-2 = <&P9_24_gpio_pu_pin>; + pinctrl-3 = <&P9_24_gpio_pd_pin>; + pinctrl-4 = <&P9_24_uart_pin>; + pinctrl-5 = <&P9_24_can_pin>; + pinctrl-6 = <&P9_24_i2c_pin>; + pinctrl-7 = <&P9_24_pruin_pin>; + }; + + P9_25_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "qep", "pruout", "pruin", "audio"; + pinctrl-0 = <&P9_25_default_pin>; + pinctrl-1 = <&P9_25_gpio_pin>; + pinctrl-2 = <&P9_25_gpio_pu_pin>; + pinctrl-3 = <&P9_25_gpio_pd_pin>; + pinctrl-4 = <&P9_25_qep_pin>; + pinctrl-5 = <&P9_25_pruout_pin>; + pinctrl-6 = <&P9_25_pruin_pin>; + pinctrl-7 = <&P9_25_audio_pin>; + }; + + P9_26_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "uart", "can", "i2c", "pruin"; + pinctrl-0 = <&P9_26_default_pin>; + pinctrl-1 = <&P9_26_gpio_pin>; + pinctrl-2 = <&P9_26_gpio_pu_pin>; + pinctrl-3 = <&P9_26_gpio_pd_pin>; + pinctrl-4 = <&P9_26_uart_pin>; + pinctrl-5 = <&P9_26_can_pin>; + pinctrl-6 = <&P9_26_i2c_pin>; + pinctrl-7 = <&P9_26_pruin_pin>; + }; + + P9_27_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "qep", "pruout", "pruin"; + pinctrl-0 = <&P9_27_default_pin>; + pinctrl-1 = <&P9_27_gpio_pin>; + pinctrl-2 = <&P9_27_gpio_pu_pin>; + pinctrl-3 = <&P9_27_gpio_pd_pin>; + pinctrl-4 = <&P9_27_qep_pin>; + pinctrl-5 = <&P9_27_pruout_pin>; + pinctrl-6 = <&P9_27_pruin_pin>; + }; + + P9_28_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm", "spi", "pwm2", "pruout", "pruin", "audio"; + pinctrl-0 = <&P9_28_default_pin>; + pinctrl-1 = <&P9_28_gpio_pin>; + pinctrl-2 = <&P9_28_gpio_pu_pin>; + pinctrl-3 = <&P9_28_gpio_pd_pin>; + pinctrl-4 = <&P9_28_pwm_pin>; + pinctrl-5 = <&P9_28_spi_pin>; + pinctrl-6 = <&P9_28_pwm2_pin>; + pinctrl-7 = <&P9_28_pruout_pin>; + pinctrl-8 = <&P9_28_pruin_pin>; + pinctrl-9 = <&P9_28_audio_pin>; + }; + + P9_29_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm", "spi", "pruout", "pruin", "audio"; + pinctrl-0 = <&P9_29_default_pin>; + pinctrl-1 = <&P9_29_gpio_pin>; + pinctrl-2 = <&P9_29_gpio_pu_pin>; + pinctrl-3 = <&P9_29_gpio_pd_pin>; + pinctrl-4 = <&P9_29_pwm_pin>; + pinctrl-5 = <&P9_29_spi_pin>; + pinctrl-6 = <&P9_29_pruout_pin>; + pinctrl-7 = <&P9_29_pruin_pin>; + pinctrl-8 = <&P9_29_audio_pin>; + }; + + P9_30_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm", "spi", "pruout", "pruin"; + pinctrl-0 = <&P9_30_default_pin>; + pinctrl-1 = <&P9_30_gpio_pin>; + pinctrl-2 = <&P9_30_gpio_pu_pin>; + pinctrl-3 = <&P9_30_gpio_pd_pin>; + pinctrl-4 = <&P9_30_pwm_pin>; + pinctrl-5 = <&P9_30_spi_pin>; + pinctrl-6 = <&P9_30_pruout_pin>; + pinctrl-7 = <&P9_30_pruin_pin>; + }; + + P9_31_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm", "spi", "pruout", "pruin", "audio"; + pinctrl-0 = <&P9_31_default_pin>; + pinctrl-1 = <&P9_31_gpio_pin>; + pinctrl-2 = <&P9_31_gpio_pu_pin>; + pinctrl-3 = <&P9_31_gpio_pd_pin>; + pinctrl-4 = <&P9_31_pwm_pin>; + pinctrl-5 = <&P9_31_spi_pin>; + pinctrl-6 = <&P9_31_pruout_pin>; + pinctrl-7 = <&P9_31_pruin_pin>; + pinctrl-8 = <&P9_31_audio_pin>; + }; + + P9_41_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "timer", "pruin"; + pinctrl-0 = <&P9_41_default_pin>; + pinctrl-1 = <&P9_41_gpio_pin>; + pinctrl-2 = <&P9_41_gpio_pu_pin>; + pinctrl-3 = <&P9_41_gpio_pd_pin>; + pinctrl-4 = <&P9_41_timer_pin>; + pinctrl-5 = <&P9_41_pruin_pin>; + }; + + P9_91_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "qep", "pruout", "pruin"; + pinctrl-0 = <&P9_91_default_pin>; + pinctrl-1 = <&P9_91_gpio_pin>; + pinctrl-2 = <&P9_91_gpio_pu_pin>; + pinctrl-3 = <&P9_91_gpio_pd_pin>; + pinctrl-4 = <&P9_91_qep_pin>; + pinctrl-5 = <&P9_91_pruout_pin>; + pinctrl-6 = <&P9_91_pruin_pin>; + }; + + P9_42_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "pwm", "uart", "spics", "spiclk"; + pinctrl-0 = <&P9_42_default_pin>; + pinctrl-1 = <&P9_42_gpio_pin>; + pinctrl-2 = <&P9_42_gpio_pu_pin>; + pinctrl-3 = <&P9_42_gpio_pd_pin>; + pinctrl-4 = <&P9_42_pwm_pin>; + pinctrl-5 = <&P9_42_uart_pin>; + pinctrl-6 = <&P9_42_spics_pin>; + pinctrl-7 = <&P9_42_spiclk_pin>; + }; + + P9_92_pinmux { + compatible = "bone-pinmux-helper"; + status = "okay"; + pinctrl-names = "default", "gpio", "gpio_pu", "gpio_pd", "qep", "pruout", "pruin"; + pinctrl-0 = <&P9_92_default_pin>; + pinctrl-1 = <&P9_92_gpio_pin>; + pinctrl-2 = <&P9_92_gpio_pu_pin>; + pinctrl-3 = <&P9_92_gpio_pd_pin>; + pinctrl-4 = <&P9_92_qep_pin>; + pinctrl-5 = <&P9_92_pruout_pin>; + pinctrl-6 = <&P9_92_pruin_pin>; + }; + + cape-universal { + compatible = "gpio-of-helper"; + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + P8_07 { + gpio-name = "P8_07"; + gpio = <&gpio2 2 0>; + input; + dir-changeable; + }; + P8_08 { + gpio-name = "P8_08"; + gpio = <&gpio2 3 0>; + input; + dir-changeable; + }; + P8_09 { + gpio-name = "P8_09"; + gpio = <&gpio2 5 0>; + input; + dir-changeable; + }; + P8_10 { + gpio-name = "P8_10"; + gpio = <&gpio2 4 0>; + input; + dir-changeable; + }; + P8_11 { + gpio-name = "P8_11"; + gpio = <&gpio1 13 0>; + input; + dir-changeable; + }; + P8_12 { + gpio-name = "P8_12"; + gpio = <&gpio1 12 0>; + input; + dir-changeable; + }; + P8_13 { + gpio-name = "P8_13"; + gpio = <&gpio0 23 0>; + input; + dir-changeable; + }; + P8_14 { + gpio-name = "P8_14"; + gpio = <&gpio0 26 0>; + input; + dir-changeable; + }; + P8_15 { + gpio-name = "P8_15"; + gpio = <&gpio1 15 0>; + input; + dir-changeable; + }; + P8_16 { + gpio-name = "P8_16"; + gpio = <&gpio1 14 0>; + input; + dir-changeable; + }; + P8_17 { + gpio-name = "P8_17"; + gpio = <&gpio0 27 0>; + input; + dir-changeable; + }; + P8_18 { + gpio-name = "P8_18"; + gpio = <&gpio2 1 0>; + input; + dir-changeable; + }; + P8_19 { + gpio-name = "P8_19"; + gpio = <&gpio0 22 0>; + input; + dir-changeable; + }; + + P8_26 { + gpio-name = "P8_26"; + gpio = <&gpio1 29 0>; + input; + dir-changeable; + }; + P8_27 { + gpio-name = "P8_27"; + gpio = <&gpio2 22 0>; + input; + dir-changeable; + }; + P8_28 { + gpio-name = "P8_28"; + gpio = <&gpio2 24 0>; + input; + dir-changeable; + }; + P8_29 { + gpio-name = "P8_29"; + gpio = <&gpio2 23 0>; + input; + dir-changeable; + }; + P8_30 { + gpio-name = "P8_30"; + gpio = <&gpio2 25 0>; + input; + dir-changeable; + }; + P8_31 { + gpio-name = "P8_31"; + gpio = <&gpio0 10 0>; + input; + dir-changeable; + }; + P8_32 { + gpio-name = "P8_32"; + gpio = <&gpio0 11 0>; + input; + dir-changeable; + }; + P8_33 { + gpio-name = "P8_33"; + gpio = <&gpio0 9 0>; + input; + dir-changeable; + }; + P8_34 { + gpio-name = "P8_34"; + gpio = <&gpio2 17 0>; + input; + dir-changeable; + }; + P8_35 { + gpio-name = "P8_35"; + gpio = <&gpio0 8 0>; + input; + dir-changeable; + }; + P8_36 { + gpio-name = "P8_36"; + gpio = <&gpio2 16 0>; + input; + dir-changeable; + }; + P8_37 { + gpio-name = "P8_37"; + gpio = <&gpio2 14 0>; + input; + dir-changeable; + }; + P8_38 { + gpio-name = "P8_38"; + gpio = <&gpio2 15 0>; + input; + dir-changeable; + }; + P8_39 { + gpio-name = "P8_39"; + gpio = <&gpio2 12 0>; + input; + dir-changeable; + }; + P8_40 { + gpio-name = "P8_40"; + gpio = <&gpio2 13 0>; + input; + dir-changeable; + }; + P8_41 { + gpio-name = "P8_41"; + gpio = <&gpio2 10 0>; + input; + dir-changeable; + }; + P8_42 { + gpio-name = "P8_42"; + gpio = <&gpio2 11 0>; + input; + dir-changeable; + }; + P8_43 { + gpio-name = "P8_43"; + gpio = <&gpio2 8 0>; + input; + dir-changeable; + }; + P8_44 { + gpio-name = "P8_44"; + gpio = <&gpio2 9 0>; + input; + dir-changeable; + }; + P8_45 { + gpio-name = "P8_45"; + gpio = <&gpio2 6 0>; + input; + dir-changeable; + }; + P8_46 { + gpio-name = "P8_46"; + gpio = <&gpio2 7 0>; + input; + dir-changeable; + }; + + + P9_11 { + gpio-name = "P9_11"; + gpio = <&gpio0 30 0>; + input; + dir-changeable; + }; + P9_12 { + gpio-name = "P9_12"; + gpio = <&gpio1 28 0>; + input; + dir-changeable; + }; + P9_13 { + gpio-name = "P9_13"; + gpio = <&gpio0 31 0>; + input; + dir-changeable; + }; + P9_14 { + gpio-name = "P9_14"; + gpio = <&gpio1 18 0>; + input; + dir-changeable; + }; + P9_15 { + gpio-name = "P9_15"; + gpio = <&gpio1 16 0>; + input; + dir-changeable; + }; + P9_16 { + gpio-name = "P9_16"; + gpio = <&gpio1 19 0>; + input; + dir-changeable; + }; + P9_17 { + gpio-name = "P9_17"; + gpio = <&gpio0 5 0>; + input; + dir-changeable; + }; + P9_18 { + gpio-name = "P9_18"; + gpio = <&gpio0 4 0>; + input; + dir-changeable; + }; + P9_19 { + gpio-name = "P9_19"; + gpio = <&gpio0 13 0>; + input; + dir-changeable; + }; + P9_20 { + gpio-name = "P9_20"; + gpio = <&gpio0 12 0>; + input; + dir-changeable; + }; + P9_21 { + gpio-name = "P9_21"; + gpio = <&gpio0 3 0>; + input; + dir-changeable; + }; + P9_22 { + gpio-name = "P9_22"; + gpio = <&gpio0 2 0>; + input; + dir-changeable; + }; + P9_23 { + gpio-name = "P9_23"; + gpio = <&gpio1 17 0>; + input; + dir-changeable; + }; + P9_24 { + gpio-name = "P9_24"; + gpio = <&gpio0 15 0>; + input; + dir-changeable; + }; + P9_25 { + gpio-name = "P9_25"; + gpio = <&gpio3 21 0>; + input; + dir-changeable; + }; + P9_26 { + gpio-name = "P9_26"; + gpio = <&gpio0 14 0>; + input; + dir-changeable; + }; + P9_27 { + gpio-name = "P9_27"; + gpio = <&gpio3 19 0>; + input; + dir-changeable; + }; + P9_28 { + gpio-name = "P9_28"; + gpio = <&gpio3 17 0>; + input; + dir-changeable; + }; + P9_29 { + gpio-name = "P9_29"; + gpio = <&gpio3 15 0>; + input; + dir-changeable; + }; + P9_30 { + gpio-name = "P9_30"; + gpio = <&gpio3 16 0>; + input; + dir-changeable; + }; + P9_31 { + gpio-name = "P9_31"; + gpio = <&gpio3 14 0>; + input; + dir-changeable; + }; + P9_41 { + gpio-name = "P9_41"; + gpio = <&gpio0 20 0>; + input; + dir-changeable; + }; + P9_91 { + gpio-name = "P9_91"; + gpio = <&gpio3 20 0>; + input; + dir-changeable; + }; + P9_42 { + gpio-name = "P9_42"; + gpio = <&gpio0 7 0>; + input; + dir-changeable; + }; + P9_92 { + gpio-name = "P9_92"; + gpio = <&gpio3 18 0>; + input; + dir-changeable; + }; + }; +}; diff --git a/arch/arm/boot/dts/am335x-bone-common.dtsi b/arch/arm/boot/dts/am335x-bone-common.dtsi index 5d370d5..b34f3bb 100644 --- a/arch/arm/boot/dts/am335x-bone-common.dtsi +++ b/arch/arm/boot/dts/am335x-bone-common.dtsi @@ -25,14 +25,14 @@ compatible = "gpio-leds"; led@2 { - label = "beaglebone:green:heartbeat"; + label = "beaglebone:green:usr0"; gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>; linux,default-trigger = "heartbeat"; default-state = "off"; }; led@3 { - label = "beaglebone:green:mmc0"; + label = "beaglebone:green:usr1"; gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>; linux,default-trigger = "mmc0"; default-state = "off"; @@ -62,117 +62,112 @@ }; &am33xx_pinmux { - pinctrl-names = "default"; - pinctrl-0 = <&clkout2_pin>; - user_leds_s0: user_leds_s0 { pinctrl-single,pins = < - 0x54 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a5.gpio1_21 */ - 0x58 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a6.gpio1_22 */ - 0x5c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a7.gpio1_23 */ - 0x60 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a8.gpio1_24 */ + AM33XX_IOPAD(0x854, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a5.gpio1_21 */ + AM33XX_IOPAD(0x858, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a6.gpio1_22 */ + AM33XX_IOPAD(0x85c, PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_a7.gpio1_23 */ + AM33XX_IOPAD(0x860, PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_a8.gpio1_24 */ >; }; i2c0_pins: pinmux_i2c0_pins { pinctrl-single,pins = < - 0x188 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda.i2c0_sda */ - 0x18c (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl.i2c0_scl */ + AM33XX_IOPAD(0x988, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda.i2c0_sda */ + AM33XX_IOPAD(0x98c, PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl.i2c0_scl */ >; }; i2c2_pins: pinmux_i2c2_pins { pinctrl-single,pins = < - 0x178 (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ - 0x17c (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ + AM33XX_IOPAD(0x978, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ + AM33XX_IOPAD(0x97c, PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ >; }; uart0_pins: pinmux_uart0_pins { pinctrl-single,pins = < - 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ - 0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ - >; - }; - - clkout2_pin: pinmux_clkout2_pin { - pinctrl-single,pins = < - 0x1b4 (PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr1.clkout2 */ + AM33XX_IOPAD(0x970, PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + AM33XX_IOPAD(0x974, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ >; }; cpsw_default: cpsw_default { pinctrl-single,pins = < /* Slave 1 */ - 0x110 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ - 0x114 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ - 0x118 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ - 0x11c (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ - 0x120 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ - 0x124 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ - 0x128 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ - 0x12c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ - 0x130 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ - 0x134 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ - 0x138 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ - 0x13c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ - 0x140 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ + 0x108 (PIN_INPUT | MUX_MODE0) /* mii1_col.mii1_col */ + 0x10c (PIN_INPUT | MUX_MODE0) /* mii1_crs.mii1_crs */ + AM33XX_IOPAD(0x910, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ + AM33XX_IOPAD(0x914, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ + AM33XX_IOPAD(0x918, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ + AM33XX_IOPAD(0x91c, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ + AM33XX_IOPAD(0x920, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ + AM33XX_IOPAD(0x924, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ + AM33XX_IOPAD(0x928, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ + AM33XX_IOPAD(0x930, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ + AM33XX_IOPAD(0x934, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ + AM33XX_IOPAD(0x938, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ + AM33XX_IOPAD(0x940, PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ >; }; cpsw_sleep: cpsw_sleep { pinctrl-single,pins = < /* Slave 1 reset value */ - 0x110 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x114 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x11c (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x120 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x124 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x128 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x12c (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x130 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x108 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x910, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x914, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x918, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x91c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x920, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x924, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x928, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x92c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x930, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x934, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x938, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x93c, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x940, PIN_INPUT_PULLDOWN | MUX_MODE7) >; }; davinci_mdio_default: davinci_mdio_default { pinctrl-single,pins = < /* MDIO */ - 0x148 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ - 0x14c (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + AM33XX_IOPAD(0x948, PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + AM33XX_IOPAD(0x94c, PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ >; }; davinci_mdio_sleep: davinci_mdio_sleep { pinctrl-single,pins = < /* MDIO reset value */ - 0x148 (PIN_INPUT_PULLDOWN | MUX_MODE7) - 0x14c (PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x948, PIN_INPUT_PULLDOWN | MUX_MODE7) + AM33XX_IOPAD(0x94c, PIN_INPUT_PULLDOWN | MUX_MODE7) >; }; mmc1_pins: pinmux_mmc1_pins { pinctrl-single,pins = < - 0x160 (PIN_INPUT | MUX_MODE7) /* GPIO0_6 */ + AM33XX_IOPAD(0x960, PIN_INPUT | MUX_MODE7) /* GPIO0_6 */ >; }; emmc_pins: pinmux_emmc_pins { pinctrl-single,pins = < - 0x80 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ - 0x84 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ - 0x00 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ - 0x04 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ - 0x08 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ - 0x0c (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ - 0x10 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad4.mmc1_dat4 */ - 0x14 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad5.mmc1_dat5 */ - 0x18 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad6.mmc1_dat6 */ - 0x1c (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad7.mmc1_dat7 */ + AM33XX_IOPAD(0x880, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ + AM33XX_IOPAD(0x884, PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ + AM33XX_IOPAD(0x800, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ + AM33XX_IOPAD(0x804, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ + AM33XX_IOPAD(0x808, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ + AM33XX_IOPAD(0x80c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ + AM33XX_IOPAD(0x810, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad4.mmc1_dat4 */ + AM33XX_IOPAD(0x814, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad5.mmc1_dat5 */ + AM33XX_IOPAD(0x818, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad6.mmc1_dat6 */ + AM33XX_IOPAD(0x81c, PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad7.mmc1_dat7 */ >; }; }; @@ -308,6 +303,9 @@ */ ti,pmic-shutdown-controller; + interrupt-parent = <&intc>; + interrupts = <7>; /* NNMI */ + regulators { dcdc1_reg: regulator@0 { regulator-name = "vdds_dpr"; @@ -359,15 +357,11 @@ phy-mode = "mii"; }; -&cpsw_emac1 { - phy_id = <&davinci_mdio>, <1>; - phy-mode = "mii"; -}; - &mac { pinctrl-names = "default", "sleep"; pinctrl-0 = <&cpsw_default>; pinctrl-1 = <&cpsw_sleep>; + slaves = <1>; status = "okay"; }; @@ -393,3 +387,32 @@ &sham { status = "okay"; }; + +&rtc { + system-power-controller; +}; + +/* the cape manager */ +/ { + bone_capemgr { + compatible = "ti,bone-capemgr"; + status = "okay"; + + nvmem-cells = <&baseboard_data &cape0_data &cape1_data &cape2_data &cape3_data>; + nvmem-cell-names = "baseboard", "slot0", "slot1", "slot2", "slot3"; + #slots = <4>; + + /* map board revisions to compatible definitions */ + baseboardmaps { + baseboard_beaglebone: board@0 { + board-name = "A335BONE"; + compatible-name = "ti,beaglebone"; + }; + + baseboard_beaglebone_black: board@1 { + board-name = "A335BNLT"; + compatible-name = "ti,beaglebone-black"; + }; + }; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bone-emmc-in-reset.dtsi b/arch/arm/boot/dts/am335x-bone-emmc-in-reset.dtsi new file mode 100644 index 0000000..7d8f673 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-emmc-in-reset.dtsi @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* standard */ + +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bone-jtag.dtsi b/arch/arm/boot/dts/am335x-bone-jtag.dtsi new file mode 100644 index 0000000..a92db8e --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-jtag.dtsi @@ -0,0 +1,20 @@ +/* + * Device Tree Source for bone jtag + * + * Copyright (C) 2015 Robert Nelson + * + * 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. + */ + +&am33xx_pinmux { + pinctrl-names = "default"; + pinctrl-0 = <&clkout2_pin>; + + clkout2_pin: pinmux_clkout2_pin { + pinctrl-single,pins = < + 0x1b4 (PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr1.clkout2 */ + >; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-can0.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-can0.dtsi new file mode 100644 index 0000000..0961216 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-can0.dtsi @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include +#include "am335x-peripheral-can0.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_19_pinmux { + * mode = "can"; + * }; + * P9_20_pinmux { + * mode = "can"; + * }; + *}; + * + *&dcan0 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + dcan0_pins: pinmux_dcan0_pins { + pinctrl-single,pins = < + /* P9_20: uart1_ctsn.d_can0_tx */ + BONE_P9_20 (PIN_OUTPUT_PULLUP | MUX_MODE2) + /* P9_19: uart1_rtsn.d_can0_rx */ + BONE_P9_19 (PIN_INPUT_PULLUP | MUX_MODE2) + >; + }; +}; + +&dcan0 { + pinctrl-0 = <&dcan0_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-can1.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-can1.dtsi new file mode 100644 index 0000000..9e26413 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-can1.dtsi @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include +#include "am335x-peripheral-can1.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_24_pinmux { + * mode = "can"; + * }; + * P9_26_pinmux { + * mode = "can"; + * }; + *}; + * + *&dcan1 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + dcan1_pins: pinmux_dcan1_pins { + pinctrl-single,pins = < + /* P9_26: uart1_rxd.d_can1_tx */ + BONE_P9_26 (PIN_OUTPUT_PULLUP | MUX_MODE2) + /* P9_24: uart1_txd.d_can1_rx */ + BONE_P9_24 (PIN_INPUT_PULLUP | MUX_MODE2) + >; + }; +}; + +&dcan1 { + pinctrl-0 = <&dcan1_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-emmc.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-emmc.dtsi new file mode 100644 index 0000000..22cf462 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-emmc.dtsi @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* Testing */ +/* lsblk */ + +#include +#include "am335x-peripheral-emmc.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P8_21_pinmux { + * state = "disabled"; + * }; + * P8_20_pinmux { + * state = "disabled"; + * }; + * P8_25_pinmux { + * state = "disabled"; + * }; + * P8_24_pinmux { + * state = "disabled"; + * }; + * P8_05_pinmux { + * state = "disabled"; + * }; + * P8_06_pinmux { + * state = "disabled"; + * }; + * P8_23_pinmux { + * state = "disabled"; + * }; + * P8_22_pinmux { + * state = "disabled"; + * }; + * P8_03_pinmux { + * state = "disabled"; + * }; + * P8_04_pinmux { + * state = "disabled"; + * }; + *}; + * + *&mmc2 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + emmc_pins: pinmux_emmc_pins { + pinctrl-single,pins = < + /* P8_21: gpmc_csn1.mmc1_clk */ + BONE_P8_21 (PIN_INPUT_PULLUP | MUX_MODE2) + /* P8_20: gpmc_csn2.mmc1_cmd */ + BONE_P8_20 (PIN_INPUT_PULLUP | MUX_MODE2) + /* P8_25: gpmc_ad0.mmc1_dat0 */ + BONE_P8_25 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_24: gpmc_ad1.mmc1_dat1 */ + BONE_P8_24 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_05: gpmc_ad2.mmc1_dat2 */ + BONE_P8_05 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_06: gpmc_ad3.mmc1_dat3 */ + BONE_P8_06 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_23: gpmc_ad4.mmc1_dat4 */ + BONE_P8_23 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_22: gpmc_ad5.mmc1_dat5 */ + BONE_P8_22 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_03: gpmc_ad6.mmc1_dat6 */ + BONE_P8_03 (PIN_INPUT_PULLUP | MUX_MODE1) + /* P8_04: gpmc_ad7.mmc1_dat7 */ + BONE_P8_04 (PIN_INPUT_PULLUP | MUX_MODE1) + >; + }; +}; + +&mmc2 { + pinctrl-0 = <&emmc_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-i2c2.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-i2c2.dtsi new file mode 100644 index 0000000..abf3b57 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-i2c2.dtsi @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include +#include "am335x-peripheral-i2c2.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_19_pinmux { + * mode = "i2c"; + * }; + * P9_20_pinmux { + * mode = "i2c"; + * }; + *}; + * + *&dcan0 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + /* P9_20: uart1_ctsn.i2c2_sda */ + BONE_P9_20 (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) + /* P9_19: uart1_rtsn.i2c2_scl */ + BONE_P9_19 (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) + >; + }; +}; + +&i2c2 { + pinctrl-0 = <&i2c2_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-panel-1024x600-24bit.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-panel-1024x600-24bit.dtsi new file mode 100644 index 0000000..65e5fbb --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-panel-1024x600-24bit.dtsi @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include +#include "am335x-peripheral-panel-1024x600-24bit.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P8_27_pinmux { + * state = "disabled"; + * }; + * P8_28_pinmux { + * state = "disabled"; + * }; + * P8_29_pinmux { + * state = "disabled"; + * }; + * P8_30_pinmux { + * state = "disabled"; + * }; + * P8_31_pinmux { + * state = "disabled"; + * }; + * P8_32_pinmux { + * state = "disabled"; + * }; + * P8_33_pinmux { + * state = "disabled"; + * }; + * P8_34_pinmux { + * state = "disabled"; + * }; + * P8_35_pinmux { + * state = "disabled"; + * }; + * P8_36_pinmux { + * state = "disabled"; + * }; + * P8_37_pinmux { + * state = "disabled"; + * }; + * P8_38_pinmux { + * state = "disabled"; + * }; + * P8_39_pinmux { + * state = "disabled"; + * }; + * P8_40_pinmux { + * state = "disabled"; + * }; + * P8_41_pinmux { + * state = "disabled"; + * }; + * P8_42_pinmux { + * state = "disabled"; + * }; + * P8_43_pinmux { + * state = "disabled"; + * }; + * P8_44_pinmux { + * state = "disabled"; + * }; + * P8_45_pinmux { + * state = "disabled"; + * }; + * P8_46_pinmux { + * state = "disabled"; + * }; + *}; + */ + +/* standard */ + +&am33xx_pinmux { + lcd_24bit_pins: pinmux_lcd_24bit_pins { + pinctrl-single,pins = < + + /* P8_45: lcd_data0.lcd_data0 */ + BONE_P8_45 (PIN_OUTPUT | MUX_MODE0) + /* P8_46: lcd_data1.lcd_data1 */ + BONE_P8_46 (PIN_OUTPUT | MUX_MODE0) + /* P8_43: lcd_data2.lcd_data2 */ + BONE_P8_43 (PIN_OUTPUT | MUX_MODE0) + /* P8_44: lcd_data3.lcd_data3 */ + BONE_P8_44 (PIN_OUTPUT | MUX_MODE0) + /* P8_41: lcd_data4.lcd_data4 */ + BONE_P8_41 (PIN_OUTPUT | MUX_MODE0) + /* P8_42: lcd_data5.lcd_data5 */ + BONE_P8_42 (PIN_OUTPUT | MUX_MODE0) + /* P8_39: lcd_data6.lcd_data6 */ + BONE_P8_39 (PIN_OUTPUT | MUX_MODE0) + /* P8_40: lcd_data7.lcd_data7 */ + BONE_P8_40 (PIN_OUTPUT | MUX_MODE0) + /* P8_37: lcd_data8.lcd_data8 */ + BONE_P8_37 (PIN_OUTPUT | MUX_MODE0) + /* P8_38: lcd_data9.lcd_data9 */ + BONE_P8_38 (PIN_OUTPUT | MUX_MODE0) + /* P8_36: lcd_data10.lcd_data10 */ + BONE_P8_36 (PIN_OUTPUT | MUX_MODE0) + /* P8_34: lcd_data11.lcd_data11 */ + BONE_P8_34 (PIN_OUTPUT | MUX_MODE0) + /* P8_35: lcd_data12.lcd_data12 */ + BONE_P8_35 (PIN_OUTPUT | MUX_MODE0) + /* P8_33: lcd_data13.lcd_data13 */ + BONE_P8_33 (PIN_OUTPUT | MUX_MODE0) + /* P8_31: lcd_data14.lcd_data14 */ + BONE_P8_31 (PIN_OUTPUT | MUX_MODE0) + /* P8_32: lcd_data15.lcd_data15 */ + BONE_P8_32 (PIN_OUTPUT | MUX_MODE0) + + /* gpmc_ad15.lcd_data16 */ + BONE_P8_15 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad14.lcd_data17 */ + BONE_P8_16 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad13.lcd_data18 */ + BONE_P8_11 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad12.lcd_data19 */ + BONE_P8_12 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad11.lcd_data20 */ + BONE_P8_17 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad10.lcd_data21 */ + BONE_P8_14 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad9.lcd_data22 */ + BONE_P8_13 (PIN_OUTPUT | MUX_MODE1) + /* gpmc_ad8.lcd_data23 */ + BONE_P8_19 (PIN_OUTPUT | MUX_MODE1) + + /* P8_27: lcd_vsync.lcd_vsync */ + BONE_P8_27 (PIN_OUTPUT | MUX_MODE0) + /* P8_29: lcd_hsync.lcd_hsync */ + BONE_P8_29 (PIN_OUTPUT | MUX_MODE0) + /* P8_28: lcd_pclk.lcd_pclk*/ + BONE_P8_28 (PIN_OUTPUT | MUX_MODE0) + /* P8_30: lcd_ac_bias_en.lcd_ac_bias_en */ + BONE_P8_30 (PIN_OUTPUT | MUX_MODE0) + >; + }; +}; + +/ { + panel { + pinctrl-0 = <&lcd_24bit_pins>; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS1.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS1.dtsi new file mode 100644 index 0000000..ae5b813 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS1.dtsi @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* Testing */ +/* sudo /sbin/getty -L ttyS1 115200 vt102 */ + +#include +#include "am335x-peripheral-ttyS1.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_24_pinmux { + * mode = "uart"; + * }; + * P9_26_pinmux { + * mode = "uart"; + * }; + *}; + * + *&uart1 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + uart1_pins: pinmux_uart1_pins { + pinctrl-single,pins = < + /* P9_24: uart1_txd.uart1_txd */ + BONE_P9_24 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) + /* P9_26: uart1_rxd.uart1_rxd */ + BONE_P9_26 (PIN_INPUT_PULLUP | MUX_MODE0) + >; + }; +}; + +&uart1 { + pinctrl-0 = <&uart1_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS2.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS2.dtsi new file mode 100644 index 0000000..5fa593a --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS2.dtsi @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* Testing */ +/* sudo /sbin/getty -L ttyS2 115200 vt102 */ + +#include +#include "am335x-peripheral-ttyS2.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_21_pinmux { + * mode = "uart"; + * }; + * P9_22_pinmux { + * mode = "uart"; + * }; + *}; + * + *&uart2 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + uart2_pins: pinmux_uart2_pins { + pinctrl-single,pins = < + /* P9_21: spi0_d0.uart2_txd */ + BONE_P9_21 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) + /* P9_22: spi0_sclk.uart2_rxd */ + BONE_P9_22 (PIN_INPUT_PULLUP | MUX_MODE1) + >; + }; +}; + +&uart2 { + pinctrl-0 = <&uart2_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS4.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS4.dtsi new file mode 100644 index 0000000..1d22a95 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS4.dtsi @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* Testing */ +/* sudo /sbin/getty -L ttyS4 115200 vt102 */ + +#include +#include "am335x-peripheral-ttyS4.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P9_11_pinmux { + * mode = "uart"; + * }; + * P9_13_pinmux { + * mode = "uart"; + * }; + *}; + * + *&uart4 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + uart4_pins: pinmux_uart4_pins { + pinctrl-single,pins = < + /* P9_11: gpmc_wait0.uart4_rxd_mux2 */ + BONE_P9_11 (PIN_INPUT_PULLUP | MUX_MODE6) + /* P9_13: gpmc_wpn.uart4_txd_mux2 */ + BONE_P9_13 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) + >; + }; +}; + +&uart4 { + pinctrl-0 = <&uart4_pins>; +}; diff --git b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS5.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS5.dtsi new file mode 100644 index 0000000..01d0aec --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS5.dtsi @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/* Testing */ +/* sudo /sbin/getty -L ttyS5 115200 vt102 */ + +#include +#include "am335x-peripheral-ttyS5.dtsi" + +/* cape universal */ + +/* + *&ocp { + * P8_37_pinmux { + * mode = "uart"; + * }; + * P8_38_pinmux { + * mode = "uart"; + * }; + *}; + * + *&uart5 { + * pinctrl-0 = <>; + *}; + * + */ + +/* standard */ + +&am33xx_pinmux { + uart5_pins: pinmux_uart5_pins { + pinctrl-single,pins = < + /* P8_38: lcd_data9.uart5_rxd */ + BONE_P8_38 (PIN_INPUT_PULLUP | MUX_MODE4) + /* P8_37: lcd_data8.uart5_txd */ + BONE_P8_37 (PIN_OUTPUT_PULLDOWN | MUX_MODE4) + >; + }; +}; + +&uart5 { + pinctrl-0 = <&uart5_pins>; +}; diff --git a/arch/arm/boot/dts/am335x-bone.dts b/arch/arm/boot/dts/am335x-bone.dts index 6b84937..b195b9e 100644 --- a/arch/arm/boot/dts/am335x-bone.dts +++ b/arch/arm/boot/dts/am335x-bone.dts @@ -9,6 +9,7 @@ #include "am33xx.dtsi" #include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" / { model = "TI AM335x BeagleBone"; diff --git b/arch/arm/boot/dts/am335x-boneblack-audio.dts b/arch/arm/boot/dts/am335x-boneblack-audio.dts new file mode 100644 index 0000000..f8a310e --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-audio.dts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; + + clk_mcasp0_fixed: clk_mcasp0_fixed { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24576000>; + }; + + clk_mcasp0: clk_mcasp0 { + #clock-cells = <0>; + compatible = "gpio-gate-clock"; + clocks = <&clk_mcasp0_fixed>; + enable-gpios = <&gpio1 27 GPIO_ACTIVE_HIGH>; /* BeagleBone Black Clk enable on GPIO1_27 */ + }; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-c.dts b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-c.dts new file mode 100644 index 0000000..f5ec278 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-c.dts @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common-no-capemgr.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +#include "am335x-cape-bbb-exp-c.dtsi" diff --git b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-r.dts b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-r.dts new file mode 100644 index 0000000..27b3b72 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-r.dts @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common-no-capemgr.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +#include "am335x-cape-bbb-exp-r.dtsi" diff --git b/arch/arm/boot/dts/am335x-boneblack-bbbmini.dts b/arch/arm/boot/dts/am335x-boneblack-bbbmini.dts new file mode 100644 index 0000000..8e41afc --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-bbbmini.dts @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * Modified by Mirko Denecke + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" + +#include +#include + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +&am33xx_pinmux { + dcan1_pins: pinmux_dcan1_pins { + pinctrl-single,pins = < + /* P9_26: uart1_rxd.d_can1_tx */ + BONE_P9_26 (PIN_OUTPUT_PULLUP | MUX_MODE2) + /* P9_24: uart1_txd.d_can1_rx */ + BONE_P9_24 (PIN_INPUT_PULLUP | MUX_MODE2) + >; + }; + + pru_pins: pinmux_pru_pins { + pinctrl-single,pins = < + 0x03c 0x35 /* ecap0_in_pwm0_out.pr1_ecap0_ecap_capin, MODE5 | INPUT_PULLUP | PRU, PPM-sum, SBUS, DSM */ + + 0x0e8 0x25 /* lcd_pclk.pr1_pru1_pru_r30_10, MODE5 | OUTPUT | PRU, CH_1 */ + 0x0e0 0x25 /* lcd_vsync.pr1_pru1_pru_r30_8, MODE5 | OUTPUT | PRU, CH_2 */ + 0x0ec 0x25 /* lcd_ac_bias_en.pr1_pru1_pru_r30_11, MODE5 | OUTPUT | PRU, CH_3 */ + 0x0e4 0x25 /* lcd_hsync.pr1_pru1_pru_r30_9, MODE5 | OUTPUT | PRU, CH_4 */ + 0x0bc 0x25 /* lcd_data7.pr1_pru1_pru_r30_7, MODE5 | OUTPUT | PRU, CH_5 */ + 0x0b8 0x25 /* lcd_data6.pr1_pru1_pru_r30_6, MODE5 | OUTPUT | PRU, CH_6 */ + 0x0b4 0x25 /* lcd_data5.pr1_pru1_pru_r30_5, MODE5 | OUTPUT | PRU, CH_7 */ + 0x0b0 0x25 /* lcd_data4.pr1_pru1_pru_r30_4, MODE5 | OUTPUT | PRU, CH_8 */ + 0x0ac 0x25 /* lcd_data3.pr1_pru1_pru_r30_3, MODE5 | OUTPUT | PRU, CH_9 */ + 0x0a8 0x25 /* lcd_data2.pr1_pru1_pru_r30_2, MODE5 | OUTPUT | PRU, CH_10 */ + 0x0a4 0x25 /* lcd_data1.pr1_pru1_pru_r30_1, MODE5 | OUTPUT | PRU, CH_11 */ + 0x0a0 0x25 /* lcd_data0.pr1_pru1_pru_r30_0, MODE5 | OUTPUT | PRU, CH_12 */ + + BONE_P8_12 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* HC-SR04 TRIG */ + BONE_P8_16 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* HC-SR04 ECHO */ + + BONE_P9_25 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* MPU9250 INT */ + >; + }; + + spi0_pins: pinmux_spi0_pins { + pinctrl-single,pins = < + /* P9_22: spi0_sclk.spi0_sclk */ + BONE_P9_22 (PIN_INPUT_PULLUP | MUX_MODE0) + /* P9_21: spi0_d0.spi0_d0 */ + BONE_P9_21 (PIN_INPUT_PULLUP | MUX_MODE0) + /* P9_18: spi0_d1.spi0_d1 */ + BONE_P9_18 (PIN_OUTPUT_PULLUP | MUX_MODE0) + /* P9_17: spi0_cs0.spi0_cs0 */ + BONE_P9_17 (PIN_OUTPUT_PULLUP | MUX_MODE0) + >; + }; + + spi1_pins: pinmux_spi1_pins { + pinctrl-single,pins = < + /* P9_31: mcasp0_aclkx.spi1_sclk */ + BONE_P9_31 (PIN_INPUT_PULLUP | MUX_MODE3) + + /* P9_29: mcasp0_fsx.spi1_d0 */ + BONE_P9_29 (PIN_INPUT_PULLUP | MUX_MODE3) + + /* P9_30: mcasp0_axr0.spi1_d1 */ + BONE_P9_30 (PIN_OUTPUT_PULLUP | MUX_MODE3) + + /* P9_28: mcasp0_ahclkr.spi1_cs0 */ + BONE_P9_28 (PIN_OUTPUT_PULLUP | MUX_MODE3) + + /* P9_19: uart1_rtsn.spi1_cs1 */ +/* BONE_P9_19 (PIN_OUTPUT_PULLUP | MUX_MODE4)*/ + + /* P9_42: ecap0_in_pwm0_out.spi1_cs1 */ + BONE_P9_42A (PIN_OUTPUT_PULLUP | MUX_MODE2) + >; + }; + + uart4_pins: pinmux_uart4_pins { + pinctrl-single,pins = < + /* P9_11: gpmc_wait0.uart4_rxd_mux2 */ + BONE_P9_11 (PIN_INPUT_PULLUP | MUX_MODE6) + /* P9_13: gpmc_wpn.uart4_txd_mux2 */ + BONE_P9_13 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) + >; + }; + + uart5_pins: pinmux_uart5_pins { + pinctrl-single,pins = < + /* P8_38: lcd_data9.uart5_rxd */ + BONE_P8_38 (PIN_INPUT_PULLUP | MUX_MODE4) + /* P8_37: lcd_data8.uart5_txd */ + BONE_P8_37 (PIN_OUTPUT_PULLDOWN | MUX_MODE4) + >; + }; +}; + +&dcan1 { + pinctrl-names = "default"; + pinctrl-0 = <&dcan1_pins>; + status = "okay"; +}; + +&i2c2 { + clock-frequency = <400000>; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins>; + status = "okay"; + + spi0_0 { + #address-cells = <1>; + #size-cells = <0>; + spi-max-frequency = <24000000>; + reg = <0>; + compatible = "spidev"; + }; +}; + +&spi1 { + pinctrl-names = "default"; + pinctrl-0 = <&spi1_pins>; + status = "okay"; + + spi1_0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + spi-max-frequency = <24000000>; + compatible = "spidev"; + }; + + spi1_1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + spi-max-frequency = <24000000>; + compatible = "spidev"; + }; +}; + +&tscadc { + adc { + ti,adc-channels = <0 1>; + }; +}; + +&pruss { + pinctrl-names = "default"; + pinctrl-0 = <&pru_pins>; + status = "okay"; +}; + +&uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&uart4_pins>; + status = "okay"; +}; + +&uart5 { + pinctrl-names = "default"; + pinctrl-0 = <&uart5_pins>; + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-cape-bone-argus.dts b/arch/arm/boot/dts/am335x-boneblack-cape-bone-argus.dts new file mode 100644 index 0000000..6f16d4c --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-cape-bone-argus.dts @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common-no-capemgr.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +&am33xx_pinmux { + nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + AM33XX_IOPAD(0x8a0, PIN_OUTPUT | MUX_MODE0) /* lcd_data0.lcd_data0 */ + AM33XX_IOPAD(0x8a4, PIN_OUTPUT | MUX_MODE0) /* lcd_data1.lcd_data1 */ + AM33XX_IOPAD(0x8a8, PIN_OUTPUT | MUX_MODE0) /* lcd_data2.lcd_data2 */ + AM33XX_IOPAD(0x8ac, PIN_OUTPUT | MUX_MODE0) /* lcd_data3.lcd_data3 */ + AM33XX_IOPAD(0x8b0, PIN_OUTPUT | MUX_MODE0) /* lcd_data4.lcd_data4 */ + AM33XX_IOPAD(0x8b4, PIN_OUTPUT | MUX_MODE0) /* lcd_data5.lcd_data5 */ + AM33XX_IOPAD(0x8b8, PIN_OUTPUT | MUX_MODE0) /* lcd_data6.lcd_data6 */ + AM33XX_IOPAD(0x8bc, PIN_OUTPUT | MUX_MODE0) /* lcd_data7.lcd_data7 */ + AM33XX_IOPAD(0x8c0, PIN_OUTPUT | MUX_MODE0) /* lcd_data8.lcd_data8 */ + AM33XX_IOPAD(0x8c4, PIN_OUTPUT | MUX_MODE0) /* lcd_data9.lcd_data9 */ + AM33XX_IOPAD(0x8c8, PIN_OUTPUT | MUX_MODE0) /* lcd_data10.lcd_data10 */ + AM33XX_IOPAD(0x8cc, PIN_OUTPUT | MUX_MODE0) /* lcd_data11.lcd_data11 */ + AM33XX_IOPAD(0x8d0, PIN_OUTPUT | MUX_MODE0) /* lcd_data12.lcd_data12 */ + AM33XX_IOPAD(0x8d4, PIN_OUTPUT | MUX_MODE0) /* lcd_data13.lcd_data13 */ + AM33XX_IOPAD(0x8d8, PIN_OUTPUT | MUX_MODE0) /* lcd_data14.lcd_data14 */ + AM33XX_IOPAD(0x8dc, PIN_OUTPUT | MUX_MODE0) /* lcd_data15.lcd_data15 */ + AM33XX_IOPAD(0x8e0, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_vsync.lcd_vsync */ + AM33XX_IOPAD(0x8e4, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_hsync.lcd_hsync */ + AM33XX_IOPAD(0x8e8, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_pclk.lcd_pclk */ + AM33XX_IOPAD(0x8ec, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_ac_bias_en.lcd_ac_bias_en */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + >; + }; +}; + +&lcdc { + status = "okay"; + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; +}; + +&i2c0 { + tda19988 { + compatible = "nxp,tda998x"; + reg = <0x70>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; + }; + }; +}; + +#include "am335x-bone-argus.dtsi" diff --git b/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.dts new file mode 100644 index 0000000..68ae67c --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.dts @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts new file mode 100644 index 0000000..6d419f7 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; + +&am33xx_pinmux { + nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + AM33XX_IOPAD(0x8a0, PIN_OUTPUT | MUX_MODE0) /* lcd_data0.lcd_data0 */ + AM33XX_IOPAD(0x8a4, PIN_OUTPUT | MUX_MODE0) /* lcd_data1.lcd_data1 */ + AM33XX_IOPAD(0x8a8, PIN_OUTPUT | MUX_MODE0) /* lcd_data2.lcd_data2 */ + AM33XX_IOPAD(0x8ac, PIN_OUTPUT | MUX_MODE0) /* lcd_data3.lcd_data3 */ + AM33XX_IOPAD(0x8b0, PIN_OUTPUT | MUX_MODE0) /* lcd_data4.lcd_data4 */ + AM33XX_IOPAD(0x8b4, PIN_OUTPUT | MUX_MODE0) /* lcd_data5.lcd_data5 */ + AM33XX_IOPAD(0x8b8, PIN_OUTPUT | MUX_MODE0) /* lcd_data6.lcd_data6 */ + AM33XX_IOPAD(0x8bc, PIN_OUTPUT | MUX_MODE0) /* lcd_data7.lcd_data7 */ + AM33XX_IOPAD(0x8c0, PIN_OUTPUT | MUX_MODE0) /* lcd_data8.lcd_data8 */ + AM33XX_IOPAD(0x8c4, PIN_OUTPUT | MUX_MODE0) /* lcd_data9.lcd_data9 */ + AM33XX_IOPAD(0x8c8, PIN_OUTPUT | MUX_MODE0) /* lcd_data10.lcd_data10 */ + AM33XX_IOPAD(0x8cc, PIN_OUTPUT | MUX_MODE0) /* lcd_data11.lcd_data11 */ + AM33XX_IOPAD(0x8d0, PIN_OUTPUT | MUX_MODE0) /* lcd_data12.lcd_data12 */ + AM33XX_IOPAD(0x8d4, PIN_OUTPUT | MUX_MODE0) /* lcd_data13.lcd_data13 */ + AM33XX_IOPAD(0x8d8, PIN_OUTPUT | MUX_MODE0) /* lcd_data14.lcd_data14 */ + AM33XX_IOPAD(0x8dc, PIN_OUTPUT | MUX_MODE0) /* lcd_data15.lcd_data15 */ + AM33XX_IOPAD(0x8e0, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_vsync.lcd_vsync */ + AM33XX_IOPAD(0x8e4, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_hsync.lcd_hsync */ + AM33XX_IOPAD(0x8e8, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_pclk.lcd_pclk */ + AM33XX_IOPAD(0x8ec, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_ac_bias_en.lcd_ac_bias_en */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + >; + }; +}; + +&lcdc { + status = "okay"; + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; +}; + +&i2c0 { + tda19988 { + compatible = "nxp,tda998x"; + reg = <0x70>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; + }; + }; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts new file mode 100644 index 0000000..6d419f7 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; + +&am33xx_pinmux { + nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + AM33XX_IOPAD(0x8a0, PIN_OUTPUT | MUX_MODE0) /* lcd_data0.lcd_data0 */ + AM33XX_IOPAD(0x8a4, PIN_OUTPUT | MUX_MODE0) /* lcd_data1.lcd_data1 */ + AM33XX_IOPAD(0x8a8, PIN_OUTPUT | MUX_MODE0) /* lcd_data2.lcd_data2 */ + AM33XX_IOPAD(0x8ac, PIN_OUTPUT | MUX_MODE0) /* lcd_data3.lcd_data3 */ + AM33XX_IOPAD(0x8b0, PIN_OUTPUT | MUX_MODE0) /* lcd_data4.lcd_data4 */ + AM33XX_IOPAD(0x8b4, PIN_OUTPUT | MUX_MODE0) /* lcd_data5.lcd_data5 */ + AM33XX_IOPAD(0x8b8, PIN_OUTPUT | MUX_MODE0) /* lcd_data6.lcd_data6 */ + AM33XX_IOPAD(0x8bc, PIN_OUTPUT | MUX_MODE0) /* lcd_data7.lcd_data7 */ + AM33XX_IOPAD(0x8c0, PIN_OUTPUT | MUX_MODE0) /* lcd_data8.lcd_data8 */ + AM33XX_IOPAD(0x8c4, PIN_OUTPUT | MUX_MODE0) /* lcd_data9.lcd_data9 */ + AM33XX_IOPAD(0x8c8, PIN_OUTPUT | MUX_MODE0) /* lcd_data10.lcd_data10 */ + AM33XX_IOPAD(0x8cc, PIN_OUTPUT | MUX_MODE0) /* lcd_data11.lcd_data11 */ + AM33XX_IOPAD(0x8d0, PIN_OUTPUT | MUX_MODE0) /* lcd_data12.lcd_data12 */ + AM33XX_IOPAD(0x8d4, PIN_OUTPUT | MUX_MODE0) /* lcd_data13.lcd_data13 */ + AM33XX_IOPAD(0x8d8, PIN_OUTPUT | MUX_MODE0) /* lcd_data14.lcd_data14 */ + AM33XX_IOPAD(0x8dc, PIN_OUTPUT | MUX_MODE0) /* lcd_data15.lcd_data15 */ + AM33XX_IOPAD(0x8e0, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_vsync.lcd_vsync */ + AM33XX_IOPAD(0x8e4, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_hsync.lcd_hsync */ + AM33XX_IOPAD(0x8e8, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_pclk.lcd_pclk */ + AM33XX_IOPAD(0x8ec, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_ac_bias_en.lcd_ac_bias_en */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + >; + }; +}; + +&lcdc { + status = "okay"; + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; +}; + +&i2c0 { + tda19988 { + compatible = "nxp,tda998x"; + reg = <0x70>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; + }; + }; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-overlay.dts new file mode 100644 index 0000000..d8051b6 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-overlay.dts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-wl1835mod-cape.dtsi b/arch/arm/boot/dts/am335x-boneblack-wl1835mod-cape.dtsi new file mode 100644 index 0000000..94caa22 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-wl1835mod-cape.dtsi @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +#include + +/ { + wlan_en_reg: fixedregulator@2 { + compatible = "regulator-fixed"; + regulator-name = "wlan-en-regulator"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + + /* WL_EN */ + gpio = <&gpio0 26 0>; + enable-active-high; + }; + + kim { + compatible = "kim"; + nshutdown_gpio = <44>; /* Bank1, pin12 */ + dev_name = "/dev/ttyO4"; + flow_cntrl = <1>; + baud_rate = <3000000>; + }; + + btwilink { + compatible = "btwilink"; + }; +}; + +&am33xx_pinmux { + bt_pins: pinmux_bt_pins { + pinctrl-single,pins = < + 0x30 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_ad12.gpio1_12 */ + >; + }; + + mmc2_pins: pinmux_mmc2_pins { + pinctrl-single,pins = < + 0x80 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ + 0x84 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ + 0x00 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ + 0x04 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ + 0x08 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ + 0x0c (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ + >; + }; + + mmc2_pins_sleep: pinmux_mmc2_pins_sleep { + pinctrl-single,pins = < + 0x80 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_csn1.mmc1_clk */ + 0x84 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_csn2.mmc1_cmd */ + 0x00 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad0.mmc1_dat0 */ + 0x04 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad1.mmc1_dat1 */ + 0x08 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad2.mmc1_dat2 */ + 0x0c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad3.mmc1_dat3 */ + >; + }; + + /* wl18xx card enable/irq GPIOs. */ + wlan_pins: pinmux_wlan_pins { + pinctrl-single,pins = < + 0x28 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad10.gpio0_26 WL_EN*/ + 0x2C (PIN_INPUT_PULLUP | MUX_MODE7) /* gpmc_ad11.gpio0_27 WL_IRQ*/ + 0x7C (PIN_OUTPUT_PULLUP | MUX_MODE0) /* gpmc_csn0.gpio1_29 BF_EN*/ + >; + }; + + /* wl18xx card enable/irq GPIOs. */ + wlan_pins_sleep: pinmux_wlan_pins_sleep { + pinctrl-single,pins = < + 0x28 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_ad10.gpio0_26 WL_EN*/ + 0x2C (PIN_INPUT_PULLUP | MUX_MODE7) /* gpmc_ad11.gpio0_27 WL_IRQ*/ + 0x7C (PIN_OUTPUT_PULLUP | MUX_MODE0) /* gpmc_csn0.gpio1_29 BF_EN*/ + >; + }; + + uart4_pins_default: pinmux_uart4_pins_default { + pinctrl-single,pins = < + 0xD0 (PIN_INPUT | MUX_MODE6) /* lcd_data12.uart4_cts */ + 0xD4 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* lcd_data13.uart4_rts */ + 0x70 (PIN_INPUT_PULLUP | MUX_MODE6) /* gpmc_wait0.uart4_rxd */ + 0x74 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* gpmc_wpn.uart4_txd */ + >; + }; + + uart4_pins_sleep: pinmux_uart4_pins_sleep { + pinctrl-single,pins = < + 0xD0 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* lcd_data12.uart4_cts */ + 0xD4 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* lcd_data13.uart4_rts */ + 0x70 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_wait0.uart4_rxd */ + 0x74 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_wpn.uart4_txd */ + >; + }; +}; + +&mmc2 { + status = "okay"; + vmmc-supply = <&wlan_en_reg>; + bus-width = <4>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&mmc2_pins &wlan_pins>; + pinctrl-1 = <&mmc2_pins_sleep &wlan_pins_sleep>; + ti,non-removable; + ti,needs-special-hs-handling; + cap-power-off-card; + keep-power-in-suspend; + + #address-cells = <1>; + #size-cells = <0>; + wlcore: wlcore@0 { + compatible = "ti,wl1835"; + reg = <2>; + interrupt-parent = <&gpio0>; + interrupts = <27 IRQ_TYPE_LEVEL_HIGH>; + }; +}; + +&uart4 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&uart4_pins_default>; + pinctrl-1 = <&uart4_pins_sleep>; + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts b/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts new file mode 100644 index 0000000..ec953a9 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common-no-capemgr.dtsi" + +/ { + model = "TI AM335x BeagleBone Black"; + compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; + +#include "am335x-boneblack-wl1835mod-cape.dtsi" diff --git a/arch/arm/boot/dts/am335x-boneblack.dts b/arch/arm/boot/dts/am335x-boneblack.dts index eadbba3..11e4a16 100644 --- a/arch/arm/boot/dts/am335x-boneblack.dts +++ b/arch/arm/boot/dts/am335x-boneblack.dts @@ -8,7 +8,10 @@ /dts-v1/; #include "am33xx.dtsi" +#include "am33xx-es2.dtsi" #include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" +/* #include "am335x-bone-jtag.dtsi" */ / { model = "TI AM335x BeagleBone Black"; @@ -36,32 +39,32 @@ &am33xx_pinmux { nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { pinctrl-single,pins = < - 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ - 0xa0 0x08 /* lcd_data0.lcd_data0, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xa4 0x08 /* lcd_data1.lcd_data1, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xa8 0x08 /* lcd_data2.lcd_data2, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xac 0x08 /* lcd_data3.lcd_data3, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xb0 0x08 /* lcd_data4.lcd_data4, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xb4 0x08 /* lcd_data5.lcd_data5, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xb8 0x08 /* lcd_data6.lcd_data6, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xbc 0x08 /* lcd_data7.lcd_data7, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xc0 0x08 /* lcd_data8.lcd_data8, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xc4 0x08 /* lcd_data9.lcd_data9, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xc8 0x08 /* lcd_data10.lcd_data10, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xcc 0x08 /* lcd_data11.lcd_data11, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xd0 0x08 /* lcd_data12.lcd_data12, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xd4 0x08 /* lcd_data13.lcd_data13, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xd8 0x08 /* lcd_data14.lcd_data14, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xdc 0x08 /* lcd_data15.lcd_data15, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ - 0xe0 0x00 /* lcd_vsync.lcd_vsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ - 0xe4 0x00 /* lcd_hsync.lcd_hsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ - 0xe8 0x00 /* lcd_pclk.lcd_pclk, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ - 0xec 0x00 /* lcd_ac_bias_en.lcd_ac_bias_en, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ + AM33XX_IOPAD(0x8a0, PIN_OUTPUT | MUX_MODE0) /* lcd_data0.lcd_data0 */ + AM33XX_IOPAD(0x8a4, PIN_OUTPUT | MUX_MODE0) /* lcd_data1.lcd_data1 */ + AM33XX_IOPAD(0x8a8, PIN_OUTPUT | MUX_MODE0) /* lcd_data2.lcd_data2 */ + AM33XX_IOPAD(0x8ac, PIN_OUTPUT | MUX_MODE0) /* lcd_data3.lcd_data3 */ + AM33XX_IOPAD(0x8b0, PIN_OUTPUT | MUX_MODE0) /* lcd_data4.lcd_data4 */ + AM33XX_IOPAD(0x8b4, PIN_OUTPUT | MUX_MODE0) /* lcd_data5.lcd_data5 */ + AM33XX_IOPAD(0x8b8, PIN_OUTPUT | MUX_MODE0) /* lcd_data6.lcd_data6 */ + AM33XX_IOPAD(0x8bc, PIN_OUTPUT | MUX_MODE0) /* lcd_data7.lcd_data7 */ + AM33XX_IOPAD(0x8c0, PIN_OUTPUT | MUX_MODE0) /* lcd_data8.lcd_data8 */ + AM33XX_IOPAD(0x8c4, PIN_OUTPUT | MUX_MODE0) /* lcd_data9.lcd_data9 */ + AM33XX_IOPAD(0x8c8, PIN_OUTPUT | MUX_MODE0) /* lcd_data10.lcd_data10 */ + AM33XX_IOPAD(0x8cc, PIN_OUTPUT | MUX_MODE0) /* lcd_data11.lcd_data11 */ + AM33XX_IOPAD(0x8d0, PIN_OUTPUT | MUX_MODE0) /* lcd_data12.lcd_data12 */ + AM33XX_IOPAD(0x8d4, PIN_OUTPUT | MUX_MODE0) /* lcd_data13.lcd_data13 */ + AM33XX_IOPAD(0x8d8, PIN_OUTPUT | MUX_MODE0) /* lcd_data14.lcd_data14 */ + AM33XX_IOPAD(0x8dc, PIN_OUTPUT | MUX_MODE0) /* lcd_data15.lcd_data15 */ + AM33XX_IOPAD(0x8e0, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_vsync.lcd_vsync */ + AM33XX_IOPAD(0x8e4, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_hsync.lcd_hsync */ + AM33XX_IOPAD(0x8e8, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_pclk.lcd_pclk */ + AM33XX_IOPAD(0x8ec, PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* lcd_ac_bias_en.lcd_ac_bias_en */ >; }; nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { pinctrl-single,pins = < - 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + AM33XX_IOPAD(0x9b0, PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr0 */ >; }; }; @@ -90,7 +93,3 @@ }; }; }; - -&rtc { - system-power-controller; -}; diff --git b/arch/arm/boot/dts/am335x-bonegreen-overlay.dts b/arch/arm/boot/dts/am335x-bonegreen-overlay.dts new file mode 100644 index 0000000..c4bb320 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-overlay.dts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "TI AM335x BeagleBone Green"; + compatible = "ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; diff --git b/arch/arm/boot/dts/am335x-bonegreen-wireless.dts b/arch/arm/boot/dts/am335x-bonegreen-wireless.dts new file mode 100644 index 0000000..44c872d --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-wireless.dts @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am335x-bonegreen-wl1835.dtsi" + +/ { + model = "TI AM335x BeagleBone Green Wireless"; + compatible = "ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +&uart3 { + status = "okay"; +}; + +&mmc3 { + status = "okay"; +}; + +&mac { + status = "disabled"; +}; diff --git b/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi b/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi new file mode 100644 index 0000000..2c38236 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi @@ -0,0 +1,140 @@ + +#include + +/ { +// wlan_en_reg: fixedregulator@2 { +// compatible = "regulator-fixed"; +// regulator-name = "wlan-en-regulator"; +// regulator-min-microvolt = <1800000>; +// regulator-max-microvolt = <1800000>; + + /* WL_EN */ +// gpio = <&gpio0 26 0>; +// enable-active-high; +// }; + + tibt { + compatible = "tibt"; + nshutdown_gpio = <60>; + dev_name = "/dev/ttyS3"; + flow_cntrl = <1>; + baud_rate = <3000000>; + }; + + btwilink { + compatible = "btwilink"; + }; +}; + +&am33xx_pinmux { + bt_pins: pinmux_bt_pins { + pinctrl-single,pins = < + 0x78 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_ad12.gpio1_28 BT_EN*/ + >; + }; + + mmc3_pins: pinmux_mmc3_pins { + pinctrl-single,pins = < + 0x8c ( PIN_INPUT_PULLUP | MUX_MODE3 ) /* gpio2_1 gpmc_clk.mmc2_clk */ + 0x88 ( PIN_INPUT_PULLUP | MUX_MODE3) /* gpio2_0 gpmc_csn3.mmc2_cmd */ + 0x30 ( PIN_INPUT_PULLUP | MUX_MODE3 ) /* gpio1_12 gpmc_ad12.mmc2_dat0 */ + 0x34 ( PIN_INPUT_PULLUP | MUX_MODE3 ) /* gpio1_13 gpmc_ad13.mmc2_dat1 */ + 0x38 ( PIN_INPUT_PULLUP | MUX_MODE3 ) /* gpio1_14 gpmc_ad14.mmc2_dat2 */ + 0x3c ( PIN_INPUT_PULLUP | MUX_MODE3 ) /* gpio1_15 gpmc_ad15.mmc2_dat3 */ + >; + }; + + mmc3_pins_sleep: pinmux_mmc3_pins_sleep { + pinctrl-single,pins = < + 0x8c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio2_1 gpmc_clk.mmc2_clk */ + 0x88 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio2_0 gpmc_csn3.mmc2_cmd */ + 0x30 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1_12 gpmc_ad12.mmc2_dat0 */ + 0x34 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1_13 gpmc_ad13.mmc2_dat1 */ + 0x38 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1_14 gpmc_ad14.mmc2_dat2 */ + 0x3c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1_15 gpmc_ad15.mmc2_dat3 */ + >; + }; + + /* wl18xx card enable/irq GPIOs. */ + wlan_pins: pinmux_wlan_pins { + pinctrl-single,pins = < + 0x28 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad10.gpio0_26 WL_EN*/ + 0x2C (PIN_INPUT_PULLUP | MUX_MODE7) /* gpmc_ad11.gpio0_27 WL_IRQ*/ + 0x7C (PIN_OUTPUT_PULLUP | MUX_MODE0) /* gpmc_csn0.gpio1_29 Cape_Buffer_EN*/ + >; + }; + + /* wl18xx card enable/irq GPIOs. */ + wlan_pins_sleep: pinmux_wlan_pins_sleep { + pinctrl-single,pins = < + 0x28 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* gpmc_ad10.gpio0_26 WL_EN*/ + 0x2C (PIN_INPUT_PULLUP | MUX_MODE7) /* gpmc_ad11.gpio0_27 WL_IRQ*/ + 0x7C (PIN_OUTPUT_PULLUP | MUX_MODE0) /* gpmc_csn0.gpio1_29 Cape_Buffer_EN*/ + >; + }; + + uart3_pins_default: pinmux_uart3_pins_default { + pinctrl-single,pins = < + 0x134 ( PIN_INPUT_PULLUP | MUX_MODE1 ) /* (L17) gmii1_rxd3.uart3_rxd */ + 0x138 ( PIN_OUTPUT_PULLDOWN | MUX_MODE1 ) /* (L16) gmii1_rxd2.uart3_txd */ + 0x148 ( PIN_INPUT | MUX_MODE3 ) /* (M17) mdio_data.uart3_ctsn */ + 0x14c ( PIN_OUTPUT_PULLDOWN | MUX_MODE3 ) /* (M18) mdio_clk.uart3_rtsn */ + >; + }; + + uart3_pins_sleep: pinmux_uart3_pins_sleep { + pinctrl-single,pins = < + 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* (L17) gmii1_rxd3.uart3_rxd */ + 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* (L16) gmii1_rxd2.uart3_txd */ + 0x148 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* (M17) mdio_data.uart3_ctsn */ + 0x14c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* (M18) mdio_clk.uart3_rtsn */ + >; + }; +}; + +&gpio0 { + wl_en { + gpio-hog; + gpios = <26 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "WL_EN"; + }; +}; + +&mmc3 { + dmas = <&edma 12 + &edma 13>; + dma-names = "tx", "rx"; + status = "okay"; + //rcn-ee: with gpio-hog wlan0 is coming up over soft-reboots.. + //vmmc-supply = <&wlan_en_reg>; + vmmc-supply = <&vmmcsd_fixed>; + bus-width = <4>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&mmc3_pins &wlan_pins>; + pinctrl-1 = <&mmc3_pins_sleep &wlan_pins_sleep>; + ti,non-removable; + ti,needs-special-hs-handling; + cap-power-off-card; + keep-power-in-suspend; + + #address-cells = <1>; + #size-cells = <0>; + wlcore: wlcore@0 { + compatible = "ti,wl1835"; + reg = <2>; + interrupt-parent = <&gpio0>; + interrupts = <27 IRQ_TYPE_LEVEL_HIGH>; + }; +}; + +&edma { + ti,edma-xbar-event-map = /bits/ 16 <1 12 2 13>; +}; + +&uart3 { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&uart3_pins_default>; + pinctrl-1 = <&uart3_pins_sleep>; + status = "okay"; +}; diff --git a/arch/arm/boot/dts/am335x-bonegreen.dts b/arch/arm/boot/dts/am335x-bonegreen.dts index 0f65bda..ef2e6cd 100644 --- a/arch/arm/boot/dts/am335x-bonegreen.dts +++ b/arch/arm/boot/dts/am335x-bonegreen.dts @@ -8,7 +8,9 @@ /dts-v1/; #include "am33xx.dtsi" +#include "am33xx-es2.dtsi" #include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" / { model = "TI AM335x BeagleBone Green"; @@ -32,22 +34,3 @@ bus-width = <8>; status = "okay"; }; - -&am33xx_pinmux { - uart2_pins: uart2_pins { - pinctrl-single,pins = < - 0x150 (PIN_INPUT | MUX_MODE1) /* spi0_sclk.uart2_rxd */ - 0x154 (PIN_OUTPUT | MUX_MODE1) /* spi0_d0.uart2_txd */ - >; - }; -}; - -&uart2 { - pinctrl-names = "default"; - pinctrl-0 = <&uart2_pins>; - status = "okay"; -}; - -&rtc { - system-power-controller; -}; diff --git b/arch/arm/boot/dts/am335x-cape-bbb-exp-c.dtsi b/arch/arm/boot/dts/am335x-cape-bbb-exp-c.dtsi new file mode 100644 index 0000000..01f9cde --- /dev/null +++ b/arch/arm/boot/dts/am335x-cape-bbb-exp-c.dtsi @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include + +#include "am335x-peripheral-can0.dtsi" +#include "am335x-bone-pinmux-can0.dtsi" + +#include "am335x-peripheral-ttyS1.dtsi" +#include "am335x-bone-pinmux-ttyS1.dtsi" + +#include "am335x-peripheral-ttyS2.dtsi" +#include "am335x-bone-pinmux-ttyS2.dtsi" + +#include "am335x-peripheral-ttyS4.dtsi" +#include "am335x-bone-pinmux-ttyS4.dtsi" + +&am33xx_pinmux { + user_leds_s1: user_leds_s1 { + pinctrl-single,pins = < + 0x98 0x7 /* gpmc_wen.gpio2_4, OUTPUT | MODE7 */ + 0x9c 0x7 /* gpmc_ben0_cle.gpio2_5, OUTPUT | MODE7 */ + >; + }; + + bb_lcd_pwm_backlight_pins: pinmux_bb_lcd_pwm_backlight_pins { + pinctrl-single,pins = < + BONE_P9_14 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* gpmc_a2.ehrpwm1a */ + >; + }; + + keymap3_pins: pinmux_keymap3_pins { + pinctrl-single,pins = < + 0x040 0x2f /* KEY_UP gpmc_a0.gpio1_16, INPUT | PULLDIS | MODE7 */ + 0x04c 0x2f /* KEY_DOWN gpmc_a3.gpio1_19, INPUT | PULLDIS | MODE7 */ + 0x078 0x2f /* KEY_RIGHT gpmc_ben1.gpio1_28, INPUT | PULLDIS | MODE7 */ + 0x164 0x2f /* KEY_LEFT ecap0_in_pwm0_out.gpio0_7, INPUT | PULLDIS | MODE7 */ + 0x1a4 0x2f /* KEY_ENTER mcasp0_fxr.gpio3_19, INPUT | PULLDIS | MODE7 */ + >; + }; + + edt_ft5306_ts_pins: pinmux_edt_ft5306_ts_pins { + pinctrl-single,pins = < + /* CAP_TSC gpmc_a1.gpio1_17, INPUT | MODE7 */ + BONE_P9_23 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + i2c1_pins: pinmux_i2c1_pins { + pinctrl-single,pins = < + /* spi0_d1.i2c1_sda, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */ + BONE_P9_18 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE2) + /* spi0_cs0.i2c1_scl, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */ + BONE_P9_17 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE2) + >; + }; + + mcasp0_pins: pinmux_mcasp0_pins { + pinctrl-single,pins = < + 0x190 0x20 /* mcasp0_aclkx.mcasp0_aclkx, INPUT | MODE0 */ + 0x194 0x20 /* mcasp0_fsx.mcasp0_fsx, INPUT | MODE0 */ + 0x198 0x20 /* mcasp0_axr0.mcasp0_axr0, INPUT | MODE0 */ + 0x19c 0x22 /* mcasp0_ahclkr.mcasp0_axr2, INPUT | MODE2 */ + >; + }; +}; + +&epwmss1 { + status = "okay"; +}; + + +&ehrpwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&bb_lcd_pwm_backlight_pins>; + status = "okay"; +}; + +&i2c1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <400000>; + + edt-ft5306@38 { + status = "okay"; + compatible = "edt,edt-ft5306", "edt,edt-ft5x06"; + pinctrl-names = "default"; + pinctrl-0 = <&edt_ft5306_ts_pins>; + + reg = <0x38>; + interrupt-parent = <&gpio1>; + interrupts = <17 0>; + + touchscreen-size-x = <1024>; + touchscreen-size-y = <600>; + }; + + tlv320aic3x: tlv320aic3x@1b { + compatible = "ti,tlv320aic3x"; + reg = <0x1b>; + status = "okay"; + }; +}; + +&mcasp0 { + pinctrl-names = "default"; + pinctrl-0 = <&mcasp0_pins>; + + status = "okay"; + + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + num-serializer = <16>; + serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ + 1 0 2 0 + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + >; + tx-num-evt = <1>; + rx-num-evt = <1>; +}; + +/ { + backlight { + status = "okay"; + compatible = "pwm-backlight"; + pwms = <&ehrpwm1 0 50000 0>; + brightness-levels = <0 51 53 56 62 75 101 152 255>; + default-brightness-level = <8>; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&keymap3_pins>; + + #address-cells = <1>; + #size-cells = <0>; + + button@1 { + debounce_interval = <50>; + linux,code = <105>; + label = "left"; + gpios = <&gpio0 7 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@2 { + debounce_interval = <50>; + linux,code = <106>; + label = "right"; + gpios = <&gpio1 28 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@3 { + debounce_interval = <50>; + linux,code = <103>; + label = "up"; + gpios = <&gpio1 16 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@4 { + debounce_interval = <50>; + linux,code = <108>; + label = "down"; + gpios = <&gpio1 19 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@5 { + debounce_interval = <50>; + linux,code = <28>; + label = "enter"; + gpios = <&gpio3 19 0x1>; + gpio-key,wakeup; + }; + }; + + gpio-leds-cape-lcd { + compatible = "gpio-leds"; + pinctrl-names = "default"; + + pinctrl-0 = <&user_leds_s1>; + + lcd-led0 { + label = "lcd:green:usr0"; + gpios = <&gpio2 4 0>; + linux,default-trigger = "heartbeat"; + default-state = "off"; + }; + + lcd-led1 { + label = "lcd:green:usr1"; + gpios = <&gpio2 5 0>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + }; + + sound { + compatible = "ti,da830-evm-audio"; + ti,model = "DA830 EVM"; + ti,audio-codec = <&tlv320aic3x>; + ti,mcasp-controller = <&mcasp0>; + ti,codec-clock-rate = <12000000>; + ti,audio-routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "MIC3L", "Mic Jack", + "MIC3R", "Mic Jack"; + }; +}; + +#include "am335x-peripheral-panel-1024x600-24bit.dtsi" +#include "am335x-bone-pinmux-panel-1024x600-24bit.dtsi" diff --git b/arch/arm/boot/dts/am335x-cape-bbb-exp-r.dtsi b/arch/arm/boot/dts/am335x-cape-bbb-exp-r.dtsi new file mode 100644 index 0000000..539409c --- /dev/null +++ b/arch/arm/boot/dts/am335x-cape-bbb-exp-r.dtsi @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include + +#include "am335x-peripheral-can0.dtsi" +#include "am335x-bone-pinmux-can0.dtsi" + +#include "am335x-peripheral-ttyS1.dtsi" +#include "am335x-bone-pinmux-ttyS1.dtsi" + +#include "am335x-peripheral-ttyS2.dtsi" +#include "am335x-bone-pinmux-ttyS2.dtsi" + +#include "am335x-peripheral-ttyS4.dtsi" +#include "am335x-bone-pinmux-ttyS4.dtsi" + +&am33xx_pinmux { + user_leds_s1: user_leds_s1 { + pinctrl-single,pins = < + 0x98 0x7 /* gpmc_wen.gpio2_4, OUTPUT | MODE7 */ + 0x9c 0x7 /* gpmc_ben0_cle.gpio2_5, OUTPUT | MODE7 */ + >; + }; + + bb_lcd_pwm_backlight_pins: pinmux_bb_lcd_pwm_backlight_pins { + pinctrl-single,pins = < + BONE_P9_14 (PIN_OUTPUT_PULLDOWN | MUX_MODE6) /* gpmc_a2.ehrpwm1a */ + >; + }; + + keymap3_pins: pinmux_keymap3_pins { + pinctrl-single,pins = < + 0x040 0x2f /* KEY_UP gpmc_a0.gpio1_16, INPUT | PULLDIS | MODE7 */ + 0x04c 0x2f /* KEY_DOWN gpmc_a3.gpio1_19, INPUT | PULLDIS | MODE7 */ + 0x078 0x2f /* KEY_RIGHT gpmc_ben1.gpio1_28, INPUT | PULLDIS | MODE7 */ + 0x164 0x2f /* KEY_LEFT ecap0_in_pwm0_out.gpio0_7, INPUT | PULLDIS | MODE7 */ + 0x1a4 0x2f /* KEY_ENTER mcasp0_fxr.gpio3_19, INPUT | PULLDIS | MODE7 */ + >; + }; + + i2c1_pins: pinmux_i2c1_pins { + pinctrl-single,pins = < + /* spi0_d1.i2c1_sda, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */ + BONE_P9_18 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE2) + /* spi0_cs0.i2c1_scl, SLEWCTRL_SLOW | INPUT_PULLUP | MODE2 */ + BONE_P9_17 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE2) + >; + }; + + mcasp0_pins: pinmux_mcasp0_pins { + pinctrl-single,pins = < + 0x190 0x20 /* mcasp0_aclkx.mcasp0_aclkx, INPUT | MODE0 */ + 0x194 0x20 /* mcasp0_fsx.mcasp0_fsx, INPUT | MODE0 */ + 0x198 0x20 /* mcasp0_axr0.mcasp0_axr0, INPUT | MODE0 */ + 0x19c 0x22 /* mcasp0_ahclkr.mcasp0_axr2, INPUT | MODE2 */ + >; + }; +}; + +&epwmss1 { + status = "okay"; +}; + + +&ehrpwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&bb_lcd_pwm_backlight_pins>; + status = "okay"; +}; + +&i2c1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&i2c1_pins>; + clock-frequency = <400000>; + + tlv320aic3x: tlv320aic3x@1b { + compatible = "ti,tlv320aic3x"; + reg = <0x1b>; + status = "okay"; + }; +}; + +&mcasp0 { + pinctrl-names = "default"; + pinctrl-0 = <&mcasp0_pins>; + + status = "okay"; + + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + num-serializer = <16>; + serial-dir = < /* 0: INACTIVE, 1: TX, 2: RX */ + 1 0 2 0 + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + >; + tx-num-evt = <1>; + rx-num-evt = <1>; +}; + +&tscadc { + status = "okay"; + tsc { + ti,wires = <4>; + ti,x-plate-resistance = <200>; + ti,coordinate-readouts = <5>; + ti,wire-config = <0x00 0x11 0x22 0x33>; + }; + + adc { + ti,adc-channels = <4 5 6 7>; + }; +}; + +/ { + backlight { + status = "okay"; + compatible = "pwm-backlight"; + pwms = <&ehrpwm1 0 50000 0>; + brightness-levels = <0 51 53 56 62 75 101 152 255>; + default-brightness-level = <8>; + }; + + gpio_keys { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&keymap3_pins>; + + #address-cells = <1>; + #size-cells = <0>; + + button@1 { + debounce_interval = <50>; + linux,code = <105>; + label = "left"; + gpios = <&gpio0 7 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@2 { + debounce_interval = <50>; + linux,code = <106>; + label = "right"; + gpios = <&gpio1 28 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@3 { + debounce_interval = <50>; + linux,code = <103>; + label = "up"; + gpios = <&gpio1 16 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@4 { + debounce_interval = <50>; + linux,code = <108>; + label = "down"; + gpios = <&gpio1 19 0x1>; + gpio-key,wakeup; + autorepeat; + }; + button@5 { + debounce_interval = <50>; + linux,code = <28>; + label = "enter"; + gpios = <&gpio3 19 0x1>; + gpio-key,wakeup; + }; + }; + + gpio-leds-cape-lcd { + compatible = "gpio-leds"; + pinctrl-names = "default"; + + pinctrl-0 = <&user_leds_s1>; + + lcd-led0 { + label = "lcd:green:usr0"; + gpios = <&gpio2 4 0>; + linux,default-trigger = "heartbeat"; + default-state = "off"; + }; + + lcd-led1 { + label = "lcd:green:usr1"; + gpios = <&gpio2 5 0>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + }; + + sound { + compatible = "ti,da830-evm-audio"; + ti,model = "DA830 EVM"; + ti,audio-codec = <&tlv320aic3x>; + ti,mcasp-controller = <&mcasp0>; + ti,codec-clock-rate = <12000000>; + ti,audio-routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "MIC3L", "Mic Jack", + "MIC3R", "Mic Jack"; + }; +}; + +#include "am335x-peripheral-panel-1024x600-24bit.dtsi" +#include "am335x-bone-pinmux-panel-1024x600-24bit.dtsi" diff --git b/arch/arm/boot/dts/am335x-cape-rtc-ds1307.dtsi b/arch/arm/boot/dts/am335x-cape-rtc-ds1307.dtsi new file mode 100644 index 0000000..bce6ac5 --- /dev/null +++ b/arch/arm/boot/dts/am335x-cape-rtc-ds1307.dtsi @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +#include + +&am33xx_pinmux { + i2c2_pins: pinmux_i2c2_pins { + pinctrl-single,pins = < + BONE_P9_20 0x73 /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) uart1_ctsn.i2c2_sda */ + BONE_P9_19 0x73 /* (SLEWCTRL_SLOW | PIN_INPUT_PULLUP | MUX_MODE3) uart1_rtsn.i2c2_scl */ + >; + }; +}; + +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + + status = "okay"; + clock-frequency = <100000>; + + rtc@68 { + compatible = "maxim,ds1307"; + reg = <0x68>; + }; +}; diff --git b/arch/arm/boot/dts/am335x-olimex-som.dts b/arch/arm/boot/dts/am335x-olimex-som.dts new file mode 100644 index 0000000..2b00ad2 --- /dev/null +++ b/arch/arm/boot/dts/am335x-olimex-som.dts @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am335x-som-common.dtsi" + +/ { + model = "Olimex AM335x SOM"; + compatible = "olimex,am335x-olimex-som", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&am33xx_pinmux { + lcd_pins_default: lcd_pins_default { + pinctrl-single,pins = < + 0x20 0x01 /* gpmc_ad8.lcd_data16, OUTPUT | MODE1 */ + 0x24 0x01 /* gpmc_ad9.lcd_data17, OUTPUT | MODE1 */ + 0x28 0x01 /* gpmc_ad10.lcd_data18, OUTPUT | MODE1 */ + 0x2c 0x01 /* gpmc_ad11.lcd_data19, OUTPUT | MODE1 */ + 0x30 0x01 /* gpmc_ad12.lcd_data20, OUTPUT | MODE1 */ + 0x34 0x01 /* gpmc_ad13.lcd_data21, OUTPUT | MODE1 */ + 0x38 0x01 /* gpmc_ad14.lcd_data22, OUTPUT | MODE1 */ + 0x3c 0x01 /* gpmc_ad15.lcd_data23, OUTPUT | MODE1 */ + 0xa0 0x00 /* lcd_data0.lcd_data0, OUTPUT | MODE0 */ + 0xa4 0x00 /* lcd_data1.lcd_data1, OUTPUT | MODE0 */ + 0xa8 0x00 /* lcd_data2.lcd_data2, OUTPUT | MODE0 */ + 0xac 0x00 /* lcd_data3.lcd_data3, OUTPUT | MODE0 */ + 0xb0 0x00 /* lcd_data4.lcd_data4, OUTPUT | MODE0 */ + 0xb4 0x00 /* lcd_data5.lcd_data5, OUTPUT | MODE0 */ + 0xb8 0x00 /* lcd_data6.lcd_data6, OUTPUT | MODE0 */ + 0xbc 0x00 /* lcd_data7.lcd_data7, OUTPUT | MODE0 */ + 0xc0 0x00 /* lcd_data8.lcd_data8, OUTPUT | MODE0 */ + 0xc4 0x00 /* lcd_data9.lcd_data9, OUTPUT | MODE0 */ + 0xc8 0x00 /* lcd_data10.lcd_data10, OUTPUT | MODE0 */ + 0xcc 0x00 /* lcd_data11.lcd_data11, OUTPUT | MODE0 */ + 0xd0 0x00 /* lcd_data12.lcd_data12, OUTPUT | MODE0 */ + 0xd4 0x00 /* lcd_data13.lcd_data13, OUTPUT | MODE0 */ + 0xd8 0x00 /* lcd_data14.lcd_data14, OUTPUT | MODE0 */ + 0xdc 0x00 /* lcd_data15.lcd_data15, OUTPUT | MODE0 */ + 0xe0 0x00 /* lcd_vsync.lcd_vsync, OUTPUT | MODE0 */ + 0xe4 0x00 /* lcd_hsync.lcd_hsync, OUTPUT | MODE0 */ + 0xe8 0x00 /* lcd_pclk.lcd_pclk, OUTPUT | MODE0 */ + 0xec 0x00 /* lcd_ac_bias_en.lcd_ac_bias_en, OUTPUT | MODE0 */ + >; + }; + + lcd_pins_sleep: lcd_pins_sleep { + pinctrl-single,pins = < + 0x20 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad8.lcd_data16 */ + 0x24 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad9.lcd_data17 */ + 0x28 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad10.lcd_data18 */ + 0x2c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad11.lcd_data19 */ + 0x30 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad12.lcd_data20 */ + 0x34 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad13.lcd_data21 */ + 0x38 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad14.lcd_data22 */ + 0x3c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpmc_ad15.lcd_data23 */ + 0xa0 (PULL_DISABLE | MUX_MODE7) /* lcd_data0.lcd_data0 */ + 0xa4 (PULL_DISABLE | MUX_MODE7) /* lcd_data1.lcd_data1 */ + 0xa8 (PULL_DISABLE | MUX_MODE7) /* lcd_data2.lcd_data2 */ + 0xac (PULL_DISABLE | MUX_MODE7) /* lcd_data3.lcd_data3 */ + 0xb0 (PULL_DISABLE | MUX_MODE7) /* lcd_data4.lcd_data4 */ + 0xb4 (PULL_DISABLE | MUX_MODE7) /* lcd_data5.lcd_data5 */ + 0xb8 (PULL_DISABLE | MUX_MODE7) /* lcd_data6.lcd_data6 */ + 0xbc (PULL_DISABLE | MUX_MODE7) /* lcd_data7.lcd_data7 */ + 0xc0 (PULL_DISABLE | MUX_MODE7) /* lcd_data8.lcd_data8 */ + 0xc4 (PULL_DISABLE | MUX_MODE7) /* lcd_data9.lcd_data9 */ + 0xc8 (PULL_DISABLE | MUX_MODE7) /* lcd_data10.lcd_data10 */ + 0xcc (PULL_DISABLE | MUX_MODE7) /* lcd_data11.lcd_data11 */ + 0xd0 (PULL_DISABLE | MUX_MODE7) /* lcd_data12.lcd_data12 */ + 0xd4 (PULL_DISABLE | MUX_MODE7) /* lcd_data13.lcd_data13 */ + 0xd8 (PULL_DISABLE | MUX_MODE7) /* lcd_data14.lcd_data14 */ + 0xdc (PULL_DISABLE | MUX_MODE7) /* lcd_data15.lcd_data15 */ + /* lcd_vsync.lcd_vsync,OUTPUT | MODE0 */ + 0xe0 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0xe4 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* lcd_hsync.lcd_hsync */ + 0xe8 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* lcd_pclk.lcd_pclk */ + /* lcd_ac_bias_en.lcd_ac_bias_en */ + 0xec (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + +}; + +&lcdc { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&lcd_pins_default>; + pinctrl-1 = <&lcd_pins_sleep>; + status = "okay"; + /* display-timings { + 480x272 { + hactive = <480>; + vactive = <272>; + hback-porch = <43>; + hfront-porch = <8>; + hsync-len = <4>; + vback-porch = <12>; + vfront-porch = <4>; + vsync-len = <10>; + clock-frequency = <9000000>; + hsync-active = <0>; + vsync-active = <0>; + }; + };*/ + + display-timings { + native-mode = <&vga1024x768>; + lcd4: 480x272 { + clock-frequency = <9000000>; + hactive = <480>; + vactive = <272>; + hfront-porch = <3>; + hback-porch = <40>; + vback-porch = <8>; + vfront-porch = <7>; + hsync-len = <2>; + vsync-len = <1>; + hsync-active = <0>; + vsync-active = <0>; + }; + lcd7: 800x480 { + clock-frequency = <33300000>; + hactive = <800>; + vactive = <480>; + hfront-porch = <210>; + hback-porch = <40>; + vback-porch = <23>; + vfront-porch = <20>; + hsync-len = <6>; + vsync-len = <2>; + hsync-active = <0>; + vsync-active = <0>; + }; + lcd10: 1024x600 { + clock-frequency = <51200000>; + hactive = <1024>; + vactive = <600>; + hfront-porch = <160>; + hback-porch = <140>; + vback-porch = <20>; + vfront-porch = <12>; + hsync-len = <20>; + vsync-len = <3>; + hsync-active = <0>; + vsync-active = <0>; + }; + + vga800x600: 800x600 { + clock-frequency = <40000000>; + hactive = <800>; + vactive = <600>; + hfront-porch = <40>; + hback-porch = <88>; + vfront-porch = <1>; + vback-porch = <23>; + hsync-len = <128>; + vsync-len = <4>; + hsync-active = <0>; + vsync-active = <0>; + }; + vga1024x768: 1024x768 { + clock-frequency = <65000000>; + hactive = <1024>; + hfront-porch = <24>; + hback-porch = <160>; + hsync-len = <136>; + vactive = <768>; + vfront-porch = <3>; + vback-porch = <29>; + vsync-len = <6>; + hsync-active = <0>; + vsync-active = <0>; + }; + }; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-can0.dtsi b/arch/arm/boot/dts/am335x-peripheral-can0.dtsi new file mode 100644 index 0000000..4335e39 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-can0.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&dcan0 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-can1.dtsi b/arch/arm/boot/dts/am335x-peripheral-can1.dtsi new file mode 100644 index 0000000..02b5bd1 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-can1.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&dcan1 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-emmc.dtsi b/arch/arm/boot/dts/am335x-peripheral-emmc.dtsi new file mode 100644 index 0000000..603f34e --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-emmc.dtsi @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + + bus-width = <8>; + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-i2c2.dtsi b/arch/arm/boot/dts/am335x-peripheral-i2c2.dtsi new file mode 100644 index 0000000..ed9a0b5 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-i2c2.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&i2c2 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-panel-1024x600-24bit.dtsi b/arch/arm/boot/dts/am335x-peripheral-panel-1024x600-24bit.dtsi new file mode 100644 index 0000000..74ddc12 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-panel-1024x600-24bit.dtsi @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&lcdc { + status = "okay"; +}; + +/ { + panel { + status = "okay"; + compatible = "ti,tilcdc,panel"; + pinctrl-names = "default"; + + panel-info { + ac-bias = <255>; + ac-bias-intrpt = <0>; + dma-burst-sz = <16>; + bpp = <32>; + fdd = <0x80>; + sync-edge = <0>; + sync-ctrl = <0>; + raster-order = <1>; + fifo-th = <0>; + }; + display-timings { + native-mode = <&timing0>; + timing0: 1024x600 { + clock-frequency = <36000000>; + hactive = <1024>; + vactive = <600>; + hfront-porch = <1>; + hback-porch = <45>; + hsync-len = <30>; + vback-porch = <22>; + vfront-porch = <12>; + vsync-len = <2>; + hsync-active = <1>; + vsync-active = <1>; + de-active = <1>; + pixelclk-active = <0>; + }; + }; + }; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-ttyS1.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS1.dtsi new file mode 100644 index 0000000..f59fa4c --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-ttyS1.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&uart1 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-ttyS2.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS2.dtsi new file mode 100644 index 0000000..a25d6cf --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-ttyS2.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&uart2 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-ttyS4.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS4.dtsi new file mode 100644 index 0000000..adc89f0 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-ttyS4.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&uart4 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-peripheral-ttyS5.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS5.dtsi new file mode 100644 index 0000000..8b42fb0 --- /dev/null +++ b/arch/arm/boot/dts/am335x-peripheral-ttyS5.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +&uart5 { + pinctrl-names = "default"; + + status = "okay"; +}; diff --git b/arch/arm/boot/dts/am335x-sancloud-bbe.dts b/arch/arm/boot/dts/am335x-sancloud-bbe.dts new file mode 100644 index 0000000..5eff816 --- /dev/null +++ b/arch/arm/boot/dts/am335x-sancloud-bbe.dts @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "am33xx.dtsi" +#include "am33xx-es2.dtsi" +#include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" + +/ { + model = "SanCloud BeagleBone Enhanced"; + compatible = "sancloud,am335x-boneenhanced", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"; +}; + +&ldo3_reg { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; +}; + +&mmc1 { + vmmc-supply = <&vmmcsd_fixed>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; + ti,vcc-aux-disable-is-sleep; +}; + +&am33xx_pinmux { + pinctrl-names = "default"; + pinctrl-0 = <&usb_hub_ctrl>; + + nxp_hdmi_bonelt_pins: nxp_hdmi_bonelt_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + 0xa0 0x08 /* lcd_data0.lcd_data0, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xa4 0x08 /* lcd_data1.lcd_data1, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xa8 0x08 /* lcd_data2.lcd_data2, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xac 0x08 /* lcd_data3.lcd_data3, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb0 0x08 /* lcd_data4.lcd_data4, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb4 0x08 /* lcd_data5.lcd_data5, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xb8 0x08 /* lcd_data6.lcd_data6, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xbc 0x08 /* lcd_data7.lcd_data7, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc0 0x08 /* lcd_data8.lcd_data8, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc4 0x08 /* lcd_data9.lcd_data9, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xc8 0x08 /* lcd_data10.lcd_data10, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xcc 0x08 /* lcd_data11.lcd_data11, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd0 0x08 /* lcd_data12.lcd_data12, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd4 0x08 /* lcd_data13.lcd_data13, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xd8 0x08 /* lcd_data14.lcd_data14, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xdc 0x08 /* lcd_data15.lcd_data15, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT | AM33XX_PULL_DISA */ + 0xe0 0x00 /* lcd_vsync.lcd_vsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xe4 0x00 /* lcd_hsync.lcd_hsync, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xe8 0x00 /* lcd_pclk.lcd_pclk, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + 0xec 0x00 /* lcd_ac_bias_en.lcd_ac_bias_en, OMAP_MUX_MODE0 | AM33XX_PIN_OUTPUT */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; + + cpsw_default: cpsw_default { + pinctrl-single,pins = < + /* Slave 1 */ + 0x114 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txen.rgmii1_tctl */ + 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxdv.rgmii1_rctl */ + 0x11c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txd3.rgmii1_td3 */ + 0x120 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txd2.rgmii1_td2 */ + 0x124 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txd1.rgmii1_td1 */ + 0x128 (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txd0.rgmii1_td0 */ + 0x12c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mii1_txclk.rgmii1_tclk */ + 0x130 (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxclk.rgmii1_rclk */ + 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxd3.rgmii1_rd3 */ + 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxd2.rgmii1_rd2 */ + 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxd1.rgmii1_rd1 */ + 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE2) /* mii1_rxd0.rgmii1_rd0 */ + >; + }; + + cpsw_sleep: cpsw_sleep { + pinctrl-single,pins = < + /* Slave 1 reset value */ + 0x114 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x11c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x120 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x124 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x128 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x12c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x130 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + davinci_mdio_default: davinci_mdio_default { + pinctrl-single,pins = < + /* MDIO */ + 0x148 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + 0x14c (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + >; + }; + + davinci_mdio_sleep: davinci_mdio_sleep { + pinctrl-single,pins = < + /* MDIO reset value */ + 0x148 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x14c (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + usb_hub_ctrl: usb_hub_ctrl { + pinctrl-single,pins = < + 0x144 (PIN_OUTPUT_PULLUP | MUX_MODE7) /* mcasp0_ahclkr.gpio3_17 */ + >; + }; + + mpu6050_pins: pinmux_mpu6050_pins { + pinctrl-single,pins = < + 0x168 (PIN_INPUT | MUX_MODE7) /* spi0_sclk.gpio0_2 */ + >; + }; + + lps3331ap_pins: pinmux_lps3331ap_pins { + pinctrl-single,pins = < + 0x6C (PIN_INPUT | MUX_MODE7) /* conf_gpmc_a11.gpio1_27 */ + >; + }; +}; + +&lcdc { + status = "okay"; + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; +}; + +&mac { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&cpsw_default>; + pinctrl-1 = <&cpsw_sleep>; +}; + +&davinci_mdio { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&davinci_mdio_default>; + pinctrl-1 = <&davinci_mdio_sleep>; +}; + +&cpsw_emac0 { + phy_id = <&davinci_mdio>, <0>; + phy-mode = "rgmii-txid"; +}; + +&i2c0 { + tda19988 { + compatible = "nxp,tda998x"; + reg = <0x70>; + pinctrl-names = "default", "off"; + pinctrl-0 = <&nxp_hdmi_bonelt_pins>; + pinctrl-1 = <&nxp_hdmi_bonelt_off_pins>; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcdc_0>; + }; + }; + }; + + lps331ap: lps331ap@5C { + compatible = "st,lps331ap"; + reg = <0x5C>; + interrupts = <0>, <1>; + }; + + mpu6050: mpu6050@68 { + compatible = "inv,mpu6050"; + reg = <0x68>; + orientation = <0xff 0 0 0 1 0 0 0 0xff>; + interrupts = <2 1>; + }; +}; + +&rtc { + system-power-controller; +}; diff --git b/arch/arm/boot/dts/am335x-som-common.dtsi b/arch/arm/boot/dts/am335x-som-common.dtsi new file mode 100644 index 0000000..fb4399b --- /dev/null +++ b/arch/arm/boot/dts/am335x-som-common.dtsi @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ + +/ { + + cpus { + cpu@0 { + cpu0-supply = <&dcdc2_fixed>; + }; + }; + + memory { + device_type = "memory"; + reg = <0x80000000 0x20000000>; /* 512 MB */ + }; + + ocp { + uart0: serial@44e09000 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + + status = "okay"; + }; + uart1: serial@48022000 { + pinctrl-names = "default"; + pinctrl-0 = <&uart1_pins>; + status = "okay"; + + }; + uart4: serial@481a8000 { + pinctrl-names = "default"; + pinctrl-0 = <&uart4_pins>; + status = "okay"; + }; + + epwmss0: epwmss@48300000 { + status = "okay"; + + ecap0: ecap@48300100 { + status = "okay"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&ecap0_pins_default>; + pinctrl-1 = <&ecap0_pins_sleep>; + }; + }; + + musb: usb@47400000 { + status = "okay"; + + control@44e10000 { + status = "okay"; + }; + + usb-phy@47401300 { + status = "okay"; + }; + + usb-phy@47401b00 { + status = "okay"; + }; + + usb@47401000 { + status = "okay"; + dr_mode = "otg"; + }; + + usb@47401800 { + status = "okay"; + dr_mode = "host"; + }; + + dma-controller@07402000 { + status = "okay"; + }; + }; + + i2c0: i2c@44e0b000 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + status = "okay"; + clock-frequency = <100000>; + + tps: tps@24 { + reg = <0x24>; + }; + }; + }; + + vmmcsd_fixed: fixedregulator@0 { + compatible = "regulator-fixed"; + regulator-name = "vmmcsd_fixed"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + dcdc2_fixed: fixedregulator@1 { + /* VDD_MPU voltage limits 0.95V - 1.325V with +/-4% tolerance */ + compatible = "regulator-fixed"; + regulator-name = "dcdc2_fixed"; + + regulator-min-microvolt = <1378000>; + regulator-max-microvolt = <1378000>; + regulator-boot-on; + regulator-always-on; + }; + + leds { + pinctrl-names = "default"; + pinctrl-0 = <&user_leds_s0>; + + compatible = "gpio-leds"; + + led@1 { + label = "led1:green:heartbeat"; + gpios = <&gpio0 19 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led@2 { + label = "led2:red:heartbeat"; + gpios = <&gpio3 20 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led@3 { + label = "led3:yello:heartbeat"; + gpios = <&gpio3 21 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + + led@4 { + label = "bkl"; + gpios = <&gpio3 19 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "default-on"; + }; + }; + + backlight { + compatible = "pwm-backlight"; + pwms = <&ecap0 0 500000 1>; + brightness-levels = < + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 33 34 35 36 37 38 39 + 40 41 42 43 44 45 46 47 48 49 + 50 51 52 53 54 55 56 57 58 59 + 60 61 62 63 64 65 66 67 68 69 + 70 71 72 73 74 75 76 77 78 79 + 80 81 82 83 84 85 86 87 88 89 + 90 91 92 93 94 95 96 97 98 99 + 100 + >; + default-brightness-level = <50>; + }; +}; + +&am33xx_pinmux { + pinctrl-names = "default"; + pinctrl-0 = <&clkout2_pin>; + + user_leds_s0: user_leds_s0 { + pinctrl-single,pins = < + 0x1b0 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* xdma_event_intr0.gpio0_19 */ + 0x198 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr0.gpio3_20 */ + 0x1a8 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr1.gpio3_21 */ + 0x1a4 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* mcasp0_fsr.gpio3[19], INPUT_PULLDOWN | MODE7 */ + >; + }; + + i2c0_pins: pinmux_i2c0_pins { + pinctrl-single,pins = < + 0x188 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda.i2c0_sda */ + 0x18c (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl.i2c0_scl */ + >; + }; + + uart0_pins: pinmux_uart0_pins { + pinctrl-single,pins = < + 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + 0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ + >; + }; + + uart1_pins: pinmux_uart1_pins { + pinctrl-single,pins = < + 0x168 (PIN_INPUT_PULLUP | MUX_MODE1) + 0x16c (PIN_OUTPUT_PULLDOWN | MUX_MODE1) + >; + }; + + uart4_pins: pinmux_uart4_pins { + pinctrl-single,pins = < + 0x180 (PIN_INPUT_PULLUP | MUX_MODE0) + 0x184 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) + >; + }; + + + + clkout2_pin: pinmux_clkout2_pin { + pinctrl-single,pins = < + 0x1b4 (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* xdma_event_intr1.clkout2 */ + >; + }; + + cpsw_default: cpsw_default { + pinctrl-single,pins = < + /* Slave 1 */ + 0x110 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxerr.mii1_rxerr */ + 0x114 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txen.mii1_txen */ + 0x118 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxdv.mii1_rxdv */ + 0x11c (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd3.mii1_txd3 */ + 0x120 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd2.mii1_txd2 */ + 0x124 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd1.mii1_txd1 */ + 0x128 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* mii1_txd0.mii1_txd0 */ + 0x12c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_txclk.mii1_txclk */ + 0x130 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxclk.mii1_rxclk */ + 0x134 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd3.mii1_rxd3 */ + 0x138 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd2.mii1_rxd2 */ + 0x13c (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd1.mii1_rxd1 */ + 0x140 (PIN_INPUT_PULLUP | MUX_MODE0) /* mii1_rxd0.mii1_rxd0 */ + + 0x040 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* gpmc_a0.gmii2_txen, OUTPUT_PULLDOWN | MODE1 */ + 0x044 (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a1.gmii2_rxdv, INPUT_PULLDOWN | MODE1 */ + 0x048 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* gpmc_a2.gmii2_txd3, OUTPUT_PULLDOWN | MODE1 */ + 0x04c (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* gpmc_a3.gmii2_txd2, OUTPUT_PULLDOWN | MODE1 */ + 0x050 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* gpmc_a4.gmii2_txd1, OUTPUT_PULLDOWN | MODE1 */ + 0x054 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* gpmc_a5.gmii2_txd0, OUTPUT_PULLDOWN | MODE1 */ + 0x058 (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a6.gmii2_txclk, INPUT_PULLDOWN | MODE1 */ + 0x05c (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a7.gmii2_rxclk, INPUT_PULLDOWN | MODE1 */ + 0x060 (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a8.gmii2_rxd3, INPUT_PULLDOWN | MODE1 */ + 0x064 (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a9.gmii2_rxd2, INPUT_PULLDOWN | MODE1 */ + 0x068 (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a10.gmii2_rxd1, INPUT_PULLDOWN | MODE1 */ + 0x06c (PIN_INPUT_PULLDOWN | MUX_MODE1 ) /* gpmc_a11.gmii2_rxd0, INPUT_PULLDOWN | MODE1 */ + 0x070 (PIN_INPUT_PULLUP | MUX_MODE1 ) /* gpmc_wait0.gmii2_crs, INPUT_PULLUP | MODE1 */ + 0x074 (PIN_INPUT_PULLUP | MUX_MODE1 ) /* gpmc_wpn.gmii2_rxer, INPUT_PULLUP | MODE1 */ + 0x078 (PIN_INPUT_PULLUP | MUX_MODE1 ) /* gpmc_ben1.gmii2_col, INPUT_PULLUP | MODE1 */ + >; + }; + + cpsw_sleep: cpsw_sleep { + pinctrl-single,pins = < + /* Slave 1 reset value */ + 0x110 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x114 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x11c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x120 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x124 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x128 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x12c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x130 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x134 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x138 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x13c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x140 (PIN_INPUT_PULLDOWN | MUX_MODE7) + + 0x40 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x44 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x48 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x4c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x50 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x54 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x58 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x5c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x60 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x64 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x68 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x6c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x070 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x074 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x078 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + davinci_mdio_default: davinci_mdio_default { + pinctrl-single,pins = < + /* MDIO */ + 0x148 (PIN_INPUT_PULLUP | SLEWCTRL_FAST | MUX_MODE0) /* mdio_data.mdio_data */ + 0x14c (PIN_OUTPUT_PULLUP | MUX_MODE0) /* mdio_clk.mdio_clk */ + >; + }; + + davinci_mdio_sleep: davinci_mdio_sleep { + pinctrl-single,pins = < + /* MDIO reset value */ + 0x148 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x14c (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + mmc1_pins_default: pinmux_mmc1_pins { + pinctrl-single,pins = < + 0x0F0 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat3.mmc0_dat3 */ + 0x0F4 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat2.mmc0_dat2 */ + 0x0F8 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat1.mmc0_dat1 */ + 0x0FC (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_dat0.mmc0_dat0 */ + 0x100 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_clk.mmc0_clk */ + 0x104 (PIN_INPUT_PULLUP | MUX_MODE0) /* mmc0_cmd.mmc0_cmd */ + 0x1A0 (PIN_INPUT_PULLUP | MUX_MODE7) /* mcasp0_aclkr.gpio3_18 */ + 0x160 (PIN_INPUT | MUX_MODE7) /* spi0_cs1.gpio0_6 */ + >; + }; + + mmc1_pins_sleep: pinmux_mmc1_pins_sleep { + pinctrl-single,pins = < + 0x0F0 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0F4 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0F8 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x0FC (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x100 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x104 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x1A0 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x160 (PIN_INPUT_PULLDOWN | MUX_MODE7) + >; + }; + + emmc_pins: pinmux_emmc_pins { + pinctrl-single,pins = < + 0x80 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn1.mmc1_clk */ + 0x84 (PIN_INPUT_PULLUP | MUX_MODE2) /* gpmc_csn2.mmc1_cmd */ + 0x00 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad0.mmc1_dat0 */ + 0x04 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad1.mmc1_dat1 */ + 0x08 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad2.mmc1_dat2 */ + 0x0c (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad3.mmc1_dat3 */ + 0x10 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad4.mmc1_dat4 */ + 0x14 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad5.mmc1_dat5 */ + 0x18 (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad6.mmc1_dat6 */ + 0x1c (PIN_INPUT_PULLUP | MUX_MODE1) /* gpmc_ad7.mmc1_dat7 */ + >; + }; + + ecap0_pins_default: backlight_pins { + pinctrl-single,pins = < + 0x164 0x0 /* eCAP0_in_PWM0_out.eCAP0_in_PWM0_out MODE0 */ + >; + }; + + ecap0_pins_sleep: ecap0_pins_sleep { + pinctrl-single,pins = < + 0x164 (PULL_DISABLE | MUX_MODE7) /* eCAP0_in_PWM0_out.eCAP0_in_PWM0_out */ + >; + }; + dcan0_default: dcan0_default_pins { + pinctrl-single,pins = < + 0x178 0x0a /* uart1_ctsn.dcan0_tx_mux2, OUTPUT | MODE2 */ + 0x17c 0x2a /* uart1_rtsn.dcan0_rx_mux2, INPUT | MODE2 */ + >; + }; + }; + +&tps { + compatible = "ti,tps65217"; + regulators { + #address-cells = <1>; + #size-cells = <0>; + + dcdc1_reg: regulator@0 { + reg = <0>; + regulator-always-on; + }; + + dcdc2_reg: regulator@1 { + reg = <1>; + /* VDD_MPU voltage limits 0.95V - 1.325V with +/-4% tolerance */ + regulator-name = "vdd_mpu"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1378000>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc3_reg: regulator@2 { + reg = <2>; + /* VDD_CORE voltage limits 0.95V - 1.1V with +/-4% tolerance */ + regulator-name = "vdd_core"; + regulator-min-microvolt = <925000>; + regulator-max-microvolt = <1150000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo1_reg: regulator@3 { + reg = <3>; + regulator-always-on; + }; + + ldo2_reg: regulator@4 { + reg = <4>; + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + reg = <5>; + regulator-always-on; + }; + + ldo4_reg: regulator@6 { + reg = <6>; + regulator-always-on; + }; + }; +}; + +&cpsw_emac0 { + phy_id = <&davinci_mdio>, <0>; + phy-mode = "mii"; +}; + +&cpsw_emac1 { + phy_id = <&davinci_mdio>, <1>; + phy-mode = "mii"; +}; + +&mac { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&cpsw_default>; + pinctrl-1 = <&cpsw_sleep>; + slaves = <2>; + dual_emac = <1>; + status = "okay"; +}; + +&davinci_mdio { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&davinci_mdio_default>; + pinctrl-1 = <&davinci_mdio_sleep>; + status = "okay"; +}; + +&mmc1 { + status = "okay"; + bus-width = <0x4>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&mmc1_pins_default>; + pinctrl-1 = <&mmc1_pins_sleep>; + cd-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + cd-inverted; +}; + +&dcan0 { + pinctrl-names = "default"; + pinctrl-0 = <&dcan0_default>; + status = "okay"; +}; + +&tscadc { + status = "okay"; + tsc { + ti,wires = <4>; + ti,x-plate-resistance = <200>; + ti,coordinate-readouts = <5>; + ti,wire-config = <0x00 0x11 0x22 0x33>; + }; + + adc { + ti,adc-channels = <0 1 2 3>; + }; +}; diff --git b/arch/arm/boot/dts/am33xx-es2.dtsi b/arch/arm/boot/dts/am33xx-es2.dtsi new file mode 100644 index 0000000..6e252d4 --- /dev/null +++ b/arch/arm/boot/dts/am33xx-es2.dtsi @@ -0,0 +1,34 @@ +/* + * Device Tree Source for AM33XX SoC + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +/ { + cpus { + cpu@0 { + /* + * To consider voltage drop between PMIC and SoC, + * tolerance value is reduced to 2% from 4% and + * voltage value is increased as a precaution. + */ + operating-points = < + /* kHz uV */ + 1000000 1325000 + 800000 1300000 + 600000 1112000 + 300000 969000 + >; + voltage-tolerance = <2>; /* 2 percentage */ + + clocks = <&dpll_mpu_ck>; + clock-names = "cpu"; + + clock-latency = <300000>; /* From omap-cpufreq driver */ + }; + }; +}; diff --git b/arch/arm/boot/dts/am33xx-overlay-edma-fix.dtsi b/arch/arm/boot/dts/am33xx-overlay-edma-fix.dtsi new file mode 100644 index 0000000..88c8d04 --- /dev/null +++ b/arch/arm/boot/dts/am33xx-overlay-edma-fix.dtsi @@ -0,0 +1,25 @@ +/* + * Device Tree Source for AM33xx Overlay EDMA fixes + * + * Copyright (C) 2015 Robert Nelson + * + * 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. + */ + +&spi0 { + status = "okay"; +}; + +&spi1 { + status = "okay"; +}; + +&mcasp0 { + status = "okay"; +}; + +&mcasp1 { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/am33xx.dtsi b/arch/arm/boot/dts/am33xx.dtsi index cfa8701..6520a20 100644 --- a/arch/arm/boot/dts/am33xx.dtsi +++ b/arch/arm/boot/dts/am33xx.dtsi @@ -90,7 +90,7 @@ * for the moment, just use a fake OCP bus entry to represent * the whole bus hierarchy. */ - ocp { + ocp: ocp { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; @@ -458,6 +458,17 @@ ti,timer-pwm; }; + pruss: pruss@4a300000 { + compatible = "ti,pruss-v2"; + ti,hwmods = "pruss"; + ti,deassert-hard-reset = "pruss", "pruss"; + reg = <0x4a300000 0x080000>; + ti,pintc-offset = <0x20000>; + interrupt-parent = <&intc>; + status = "disabled"; + interrupts = <20 21 22 23 24 25 26 27>; + }; + rtc: rtc@44e3e000 { compatible = "ti,am3352-rtc", "ti,da830-rtc"; reg = <0x44e3e000 0x1000>; @@ -654,6 +665,15 @@ ti,hwmods = "ehrpwm0"; status = "disabled"; }; + + eqep0: eqep@0x48300180 { + compatible = "ti,am33xx-eqep"; + reg = <0x48300180 0x80>; + interrupt-parent = <&intc>; + interrupts = <79>; + ti,hwmods = "eqep0"; + status = "disabled"; + }; }; epwmss1: epwmss@48302000 { @@ -684,6 +704,15 @@ ti,hwmods = "ehrpwm1"; status = "disabled"; }; + + eqep1: eqep@0x48302180 { + compatible = "ti,am33xx-eqep"; + reg = <0x48302180 0x80>; + interrupt-parent = <&intc>; + interrupts = <88>; + ti,hwmods = "eqep1"; + status = "disabled"; + }; }; epwmss2: epwmss@48304000 { @@ -714,6 +743,15 @@ ti,hwmods = "ehrpwm2"; status = "disabled"; }; + + eqep2: eqep@0x48304180 { + compatible = "ti,am33xx-eqep"; + reg = <0x48304180 0x80>; + interrupt-parent = <&intc>; + interrupts = <89>; + ti,hwmods = "eqep2"; + status = "disabled"; + }; }; mac: ethernet@4a100000 { diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi index c2a03c7..ef540dc 100644 --- a/arch/arm/boot/dts/dra7.dtsi +++ b/arch/arm/boot/dts/dra7.dtsi @@ -297,6 +297,13 @@ reg = <0x40d00000 0x100>; }; + dra7_iodelay_core: padconf@4844a000 { + compatible = "ti,dra7-iodelay"; + reg = <0x4844a000 0x0d1c>; + #address-cells = <1>; + #size-cells = <0>; + }; + sdma: dma-controller@4a056000 { compatible = "ti,omap4430-sdma"; reg = <0x4a056000 0x1000>; diff --git b/arch/arm/boot/dts/imx6dl-sabresd-wl1835.dts b/arch/arm/boot/dts/imx6dl-sabresd-wl1835.dts new file mode 100644 index 0000000..37721c1 --- /dev/null +++ b/arch/arm/boot/dts/imx6dl-sabresd-wl1835.dts @@ -0,0 +1,17 @@ +/* + * Copyright (C) 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. + */ + +/dts-v1/; + +#include "imx6dl.dtsi" +#include "imx6qdl-sabresd-wl1835.dtsi" + +/ { + model = "Freescale i.MX6 DualLite SABRE Smart Device Board"; + compatible = "fsl,imx6dl-sabresd", "fsl,imx6dl"; +}; diff --git a/arch/arm/boot/dts/imx6dl.dtsi b/arch/arm/boot/dts/imx6dl.dtsi index 4b0ec07..51c517a 100644 --- a/arch/arm/boot/dts/imx6dl.dtsi +++ b/arch/arm/boot/dts/imx6dl.dtsi @@ -104,6 +104,11 @@ compatible = "fsl,imx-display-subsystem"; ports = <&ipu1_di0>, <&ipu1_di1>; }; + + gpu-subsystem { + compatible = "fsl,imx-gpu-subsystem"; + cores = <&gpu_2d>, <&gpu_3d>; + }; }; &gpt { diff --git b/arch/arm/boot/dts/imx6q-ccimx6sbc.dts b/arch/arm/boot/dts/imx6q-ccimx6sbc.dts new file mode 100644 index 0000000..d249240 --- /dev/null +++ b/arch/arm/boot/dts/imx6q-ccimx6sbc.dts @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2015 Robert Nelson (robertcnelson@gmail.com) + * + * This file is dual-licensed: you can use it either under the terms + * of the GPL or the X11 license, at your option. Note that this dual + * licensing only applies to this file, and not this project as a + * whole. + * + * a) This file 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. + * + * This file 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. + * + * Or, alternatively + * + * b) Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED , WITHOUT WARRANTY OF ANY KIND + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +/dts-v1/; + +#include "imx6q.dtsi" +#include + +/ { + model = "Digi ConnectCore-i.MX6 SBC Board"; + compatible = "digi,connectcore/q", "fsl,imx6q"; + + chosen { + stdout-path = &uart4; + }; + + memory { + reg = <0x10000000 0x40000000>; + }; + + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + reg_usbh1_reset: regulator@1 { + compatible = "regulator-fixed"; + reg = <1>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbh1>; + regulator-name = "usbh1_reset"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio3 10 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + reg_usb_otg_vbus: regulator@2 { + compatible = "regulator-fixed"; + reg = <2>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbotg>; + regulator-name = "usb_otg_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio3 22 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + }; +}; + +&fec { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_enet>; + phy-mode = "rgmii"; + phy-reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>; + status = "okay"; +}; + +&hdmi { + ddc-i2c-bus = <&i2c3>; + status = "okay"; +}; + +&i2c2 { + clock-frequency = <400000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c2>; + status = "okay"; + + pmic@58 { + compatible = "dlg,da9063"; + reg = <0x58>; + interrupt-parent = <&gpio1>; + interrupts = <17 0x8>; /* active-low GPIO0_17 */ + + regulators { + vdd_3v3_reg: bperi { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + ldo3_reg: ldo3 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + ldo4_reg: ldo4 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + ldo6_reg: ldo6 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + ldo7_reg: ldo7 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo8_reg: ldo8 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + }; + }; +}; + +&i2c3 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c3>; + status = "okay"; +}; + +&iomuxc { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hog>; + + imx6qdl-ccimx6sbc { + pinctrl_hog: hoggrp { + fsl,pins = < + /* da9063*/ + MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000 + >; + }; + + pinctrl_enet: enetgrp { + fsl,pins = < + MX6QDL_PAD_ENET_MDIO__ENET_MDIO 0x100b0 + MX6QDL_PAD_ENET_MDC__ENET_MDC 0x100b0 + MX6QDL_PAD_RGMII_TXC__RGMII_TXC 0x100b0 + MX6QDL_PAD_RGMII_TD0__RGMII_TD0 0x100b0 + MX6QDL_PAD_RGMII_TD1__RGMII_TD1 0x100b0 + MX6QDL_PAD_RGMII_TD2__RGMII_TD2 0x100b0 + MX6QDL_PAD_RGMII_TD3__RGMII_TD3 0x100b0 + MX6QDL_PAD_RGMII_TX_CTL__RGMII_TX_CTL 0x100b0 + MX6QDL_PAD_ENET_REF_CLK__ENET_TX_CLK 0x100b0 + MX6QDL_PAD_RGMII_RXC__RGMII_RXC 0x1b0b0 + MX6QDL_PAD_RGMII_RD0__RGMII_RD0 0x1b0b0 + MX6QDL_PAD_RGMII_RD1__RGMII_RD1 0x1b0b0 + MX6QDL_PAD_RGMII_RD2__RGMII_RD2 0x1b0b0 + MX6QDL_PAD_RGMII_RD3__RGMII_RD3 0x1b0b0 + MX6QDL_PAD_RGMII_RX_CTL__RGMII_RX_CTL 0x1b0b0 + /* Phy reset */ + MX6QDL_PAD_ENET_CRS_DV__GPIO1_IO25 0x000b0 + >; + }; + + pinctrl_i2c2: i2c2grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL3__I2C2_SCL 0x4001b8b1 + MX6QDL_PAD_KEY_ROW3__I2C2_SDA 0x4001b8b1 + >; + }; + + pinctrl_i2c3: i2c3grp { + fsl,pins = < + MX6QDL_PAD_GPIO_5__I2C3_SCL 0x4001b8b1 + MX6QDL_PAD_GPIO_6__I2C3_SDA 0x4001b8b1 + >; + }; + + pinctrl_uart4: uart4grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL0__UART4_TX_DATA 0x1b0b1 + MX6QDL_PAD_KEY_ROW0__UART4_RX_DATA 0x1b0b1 + >; + }; + + pinctrl_usbh1: usbh1grp { + fsl,pins = < + /* need to force low for hub reset */ + MX6QDL_PAD_EIM_DA10__GPIO3_IO10 0x10b0 + >; + }; + + pinctrl_usbotg: usbotggrp { + fsl,pins = < + MX6QDL_PAD_GPIO_1__USB_OTG_ID 0x17059 + MX6QDL_PAD_EIM_D21__USB_OTG_OC 0x1b0b0 + /* power enable, high active */ + MX6QDL_PAD_EIM_D22__GPIO3_IO22 0x10b0 + >; + }; + + pinctrl_usdhc2: usdhc2grp { + fsl,pins = < + MX6QDL_PAD_SD2_CMD__SD2_CMD 0x17059 + MX6QDL_PAD_SD2_CLK__SD2_CLK 0x10059 + MX6QDL_PAD_SD2_DAT0__SD2_DATA0 0x17059 + MX6QDL_PAD_SD2_DAT1__SD2_DATA1 0x17059 + MX6QDL_PAD_SD2_DAT2__SD2_DATA2 0x17059 + MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x17059 + >; + }; + + pinctrl_usdhc4: usdhc4grp { + fsl,pins = < + MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059 + MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059 + MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059 + MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059 + MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059 + MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059 + MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059 + MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059 + MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059 + MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 + >; + }; + }; +}; + +&sata { + status = "okay"; +}; + +&ssi1 { + status = "okay"; +}; + +&uart4 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart4>; + status = "okay"; +}; + +&usbh1 { + vbus-supply = <®_usbh1_reset>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbh1>; + status = "okay"; +}; + +&usbotg { + vbus-supply = <®_usb_otg_vbus>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbotg>; + status = "okay"; +}; + +&usdhc2 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc2>; + bus-width = <4>; + broken-cd; /* cd & wp, is not wired up on this board */ + status = "okay"; +}; + +&usdhc4 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc4>; + bus-width = <8>; + non-removable; + status = "okay"; +}; diff --git b/arch/arm/boot/dts/imx6q-sabresd-wl1835.dts b/arch/arm/boot/dts/imx6q-sabresd-wl1835.dts new file mode 100644 index 0000000..d88d9e2 --- /dev/null +++ b/arch/arm/boot/dts/imx6q-sabresd-wl1835.dts @@ -0,0 +1,25 @@ +/* + * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/dts-v1/; + +#include "imx6q.dtsi" +#include "imx6qdl-sabresd-wl1835.dtsi" + +/ { + model = "Freescale i.MX6 Quad SABRE Smart Device Board"; + compatible = "fsl,imx6q-sabresd", "fsl,imx6q"; +}; + +&sata { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/imx6q.dtsi b/arch/arm/boot/dts/imx6q.dtsi index 399103b..77d618b 100644 --- a/arch/arm/boot/dts/imx6q.dtsi +++ b/arch/arm/boot/dts/imx6q.dtsi @@ -153,6 +153,16 @@ status = "disabled"; }; + gpu_vg: gpu@02204000 { + compatible = "vivante,gc"; + reg = <0x02204000 0x4000>; + interrupts = <0 11 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks IMX6QDL_CLK_OPENVG_AXI>, + <&clks IMX6QDL_CLK_GPU2D_CORE>; + clock-names = "bus", "core"; + power-domains = <&gpc 1>; + }; + ipu2: ipu@02800000 { #address-cells = <1>; #size-cells = <0>; @@ -225,6 +235,11 @@ compatible = "fsl,imx-display-subsystem"; ports = <&ipu1_di0>, <&ipu1_di1>, <&ipu2_di0>, <&ipu2_di1>; }; + + gpu-subsystem { + compatible = "fsl,imx-gpu-subsystem"; + cores = <&gpu_2d>, <&gpu_3d>, <&gpu_vg>; + }; }; &hdmi { diff --git b/arch/arm/boot/dts/imx6qdl-sabresd-wl1835.dtsi b/arch/arm/boot/dts/imx6qdl-sabresd-wl1835.dtsi new file mode 100644 index 0000000..862dfbd --- /dev/null +++ b/arch/arm/boot/dts/imx6qdl-sabresd-wl1835.dtsi @@ -0,0 +1,646 @@ +/* Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2011 Linaro Ltd. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include + +/ { + chosen { + stdout-path = &uart1; + }; + + memory { + reg = <0x10000000 0x40000000>; + }; + + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + reg_usb_otg_vbus: regulator@0 { + compatible = "regulator-fixed"; + reg = <0>; + regulator-name = "usb_otg_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio3 22 0>; + enable-active-high; + vin-supply = <&swbst_reg>; + }; + + reg_usb_h1_vbus: regulator@1 { + compatible = "regulator-fixed"; + reg = <1>; + regulator-name = "usb_h1_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio1 29 0>; + enable-active-high; + vin-supply = <&swbst_reg>; + }; + + reg_audio: regulator@2 { + compatible = "regulator-fixed"; + reg = <2>; + regulator-name = "wm8962-supply"; + gpio = <&gpio4 10 0>; + enable-active-high; + }; + + reg_pcie: regulator@3 { + compatible = "regulator-fixed"; + reg = <3>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pcie_reg>; + regulator-name = "MPCIE_3V3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio3 19 0>; + regulator-always-on; + enable-active-high; + }; + + wlan_en_reg: regulator@4 { + compatible = "regulator-fixed"; + regulator-name = "wlan-en-regulator"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* WLAN_EN GPIO for this board – Bank4, pin7 */ + gpio = <&gpio4 7 0>; + enable-active-high; + }; + }; + + gpio-keys { + compatible = "gpio-keys"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpio_keys>; + + power { + label = "Power Button"; + gpios = <&gpio3 29 GPIO_ACTIVE_LOW>; + gpio-key,wakeup; + linux,code = ; + }; + + volume-up { + label = "Volume Up"; + gpios = <&gpio1 4 GPIO_ACTIVE_LOW>; + gpio-key,wakeup; + linux,code = ; + }; + + volume-down { + label = "Volume Down"; + gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; + gpio-key,wakeup; + linux,code = ; + }; + }; + + sound { + compatible = "fsl,imx6q-sabresd-wm8962", + "fsl,imx-audio-wm8962"; + model = "wm8962-audio"; + ssi-controller = <&ssi2>; + audio-codec = <&codec>; + audio-routing = + "Headphone Jack", "HPOUTL", + "Headphone Jack", "HPOUTR", + "Ext Spk", "SPKOUTL", + "Ext Spk", "SPKOUTR", + "AMIC", "MICBIAS", + "IN3R", "AMIC"; + mux-int-port = <2>; + mux-ext-port = <3>; + }; + + backlight { + compatible = "pwm-backlight"; + pwms = <&pwm1 0 5000000>; + brightness-levels = <0 4 8 16 32 64 128 255>; + default-brightness-level = <7>; + status = "okay"; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_gpio_leds>; + +/* red_led gpio gpio1_2 is used for bt_enable + red { + gpios = <&gpio1 2 0>; + default-state = "on"; + }; +*/ + }; + + kim { + compatible = "kim"; + nshutdown_gpio = <2>; + dev_name = "/dev/ttymxc4"; + flow_cntrl = <1>; + baud_rate = <3000000>; + }; + + btwilink { + compatible = "btwilink"; + }; +}; + +&audmux { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_audmux>; + status = "okay"; +}; + +/* +&ecspi1 { + fsl,spi-num-chipselects = <1>; + cs-gpios = <&gpio4 9 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ecspi1>; + status = "okay"; + + flash: m25p80@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "st,m25p32"; + spi-max-frequency = <20000000>; + reg = <0>; + }; +}; +*/ + +&fec { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_enet>; + phy-mode = "rgmii"; + phy-reset-gpios = <&gpio1 25 0>; + status = "okay"; +}; + +&hdmi { + ddc-i2c-bus = <&i2c2>; + status = "okay"; +}; + +&i2c1 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c1>; + status = "okay"; + + codec: wm8962@1a { + compatible = "wlf,wm8962"; + reg = <0x1a>; + clocks = <&clks IMX6QDL_CLK_CKO>; + DCVDD-supply = <®_audio>; + DBVDD-supply = <®_audio>; + AVDD-supply = <®_audio>; + CPVDD-supply = <®_audio>; + MICVDD-supply = <®_audio>; + PLLVDD-supply = <®_audio>; + SPKVDD1-supply = <®_audio>; + SPKVDD2-supply = <®_audio>; + gpio-cfg = < + 0x0000 /* 0:Default */ + 0x0000 /* 1:Default */ + 0x0013 /* 2:FN_DMICCLK */ + 0x0000 /* 3:Default */ + 0x8014 /* 4:FN_DMICCDAT */ + 0x0000 /* 5:Default */ + >; + }; +}; + +&i2c2 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c2>; + status = "okay"; + + pmic: pfuze100@08 { + compatible = "fsl,pfuze100"; + reg = <0x08>; + + regulators { + sw1a_reg: sw1ab { + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1875000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <6250>; + }; + + sw1c_reg: sw1c { + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1875000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <6250>; + }; + + sw2_reg: sw2 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + sw3a_reg: sw3a { + regulator-min-microvolt = <400000>; + regulator-max-microvolt = <1975000>; + regulator-boot-on; + regulator-always-on; + }; + + sw3b_reg: sw3b { + regulator-min-microvolt = <400000>; + regulator-max-microvolt = <1975000>; + regulator-boot-on; + regulator-always-on; + }; + + sw4_reg: sw4 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + }; + + swbst_reg: swbst { + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5150000>; + }; + + snvs_reg: vsnvs { + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + }; + + vref_reg: vrefddr { + regulator-boot-on; + regulator-always-on; + }; + + vgen1_reg: vgen1 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1550000>; + }; + + vgen2_reg: vgen2 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1550000>; + }; + + vgen3_reg: vgen3 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + }; + + vgen4_reg: vgen4 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vgen5_reg: vgen5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vgen6_reg: vgen6 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + }; + }; +}; + +&i2c3 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c3>; + status = "okay"; + + egalax_ts@04 { + compatible = "eeti,egalax_ts"; + reg = <0x04>; + interrupt-parent = <&gpio6>; + interrupts = <7 2>; + wakeup-gpios = <&gpio6 7 0>; + }; +}; + +&iomuxc { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hog>; + + imx6qdl-sabresd { + pinctrl_hog: hoggrp { + fsl,pins = < + MX6QDL_PAD_NANDF_D0__GPIO2_IO00 0x1b0b0 + MX6QDL_PAD_NANDF_D1__GPIO2_IO01 0x1b0b0 + MX6QDL_PAD_NANDF_D2__GPIO2_IO02 0x1b0b0 + MX6QDL_PAD_NANDF_D3__GPIO2_IO03 0x1b0b0 + MX6QDL_PAD_GPIO_0__CCM_CLKO1 0x130b0 + MX6QDL_PAD_NANDF_CLE__GPIO6_IO07 0x1b0b0 + MX6QDL_PAD_ENET_TXD1__GPIO1_IO29 0x1b0b0 + MX6QDL_PAD_EIM_D22__GPIO3_IO22 0x1b0b0 + MX6QDL_PAD_ENET_CRS_DV__GPIO1_IO25 0x1b0b0 + MX6QDL_PAD_KEY_ROW0__GPIO4_IO07 0x13059 // reserve two pins wl8 gpio, this is pulled low at reset for WL_EN + MX6QDL_PAD_KEY_COL0__GPIO4_IO06 0x13059 // this is for WL_IRQ which driver will configure as an input with a pull down + >; + }; + + pinctrl_audmux: audmuxgrp { + fsl,pins = < + MX6QDL_PAD_CSI0_DAT7__AUD3_RXD 0x130b0 + MX6QDL_PAD_CSI0_DAT4__AUD3_TXC 0x130b0 + MX6QDL_PAD_CSI0_DAT5__AUD3_TXD 0x110b0 + MX6QDL_PAD_CSI0_DAT6__AUD3_TXFS 0x130b0 + >; + }; + + pinctrl_ecspi1: ecspi1grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL1__ECSPI1_MISO 0x100b1 + MX6QDL_PAD_KEY_ROW0__ECSPI1_MOSI 0x100b1 + MX6QDL_PAD_KEY_COL0__ECSPI1_SCLK 0x100b1 + MX6QDL_PAD_KEY_ROW1__GPIO4_IO09 0x1b0b0 + >; + }; + + pinctrl_enet: enetgrp { + fsl,pins = < + MX6QDL_PAD_ENET_MDIO__ENET_MDIO 0x1b0b0 + MX6QDL_PAD_ENET_MDC__ENET_MDC 0x1b0b0 + MX6QDL_PAD_RGMII_TXC__RGMII_TXC 0x1b0b0 + MX6QDL_PAD_RGMII_TD0__RGMII_TD0 0x1b0b0 + MX6QDL_PAD_RGMII_TD1__RGMII_TD1 0x1b0b0 + MX6QDL_PAD_RGMII_TD2__RGMII_TD2 0x1b0b0 + MX6QDL_PAD_RGMII_TD3__RGMII_TD3 0x1b0b0 + MX6QDL_PAD_RGMII_TX_CTL__RGMII_TX_CTL 0x1b0b0 + MX6QDL_PAD_ENET_REF_CLK__ENET_TX_CLK 0x1b0b0 + MX6QDL_PAD_RGMII_RXC__RGMII_RXC 0x1b0b0 + MX6QDL_PAD_RGMII_RD0__RGMII_RD0 0x1b0b0 + MX6QDL_PAD_RGMII_RD1__RGMII_RD1 0x1b0b0 + MX6QDL_PAD_RGMII_RD2__RGMII_RD2 0x1b0b0 + MX6QDL_PAD_RGMII_RD3__RGMII_RD3 0x1b0b0 + MX6QDL_PAD_RGMII_RX_CTL__RGMII_RX_CTL 0x1b0b0 + MX6QDL_PAD_GPIO_16__ENET_REF_CLK 0x4001b0a8 + >; + }; + + pinctrl_gpio_keys: gpio_keysgrp { + fsl,pins = < + MX6QDL_PAD_EIM_D29__GPIO3_IO29 0x1b0b0 + MX6QDL_PAD_GPIO_4__GPIO1_IO04 0x1b0b0 + MX6QDL_PAD_GPIO_5__GPIO1_IO05 0x1b0b0 + >; + }; + + pinctrl_i2c1: i2c1grp { + fsl,pins = < + MX6QDL_PAD_CSI0_DAT8__I2C1_SDA 0x4001b8b1 + MX6QDL_PAD_CSI0_DAT9__I2C1_SCL 0x4001b8b1 + >; + }; + + pinctrl_i2c2: i2c2grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL3__I2C2_SCL 0x4001b8b1 + MX6QDL_PAD_KEY_ROW3__I2C2_SDA 0x4001b8b1 + >; + }; + + pinctrl_i2c3: i2c3grp { + fsl,pins = < + MX6QDL_PAD_GPIO_3__I2C3_SCL 0x4001b8b1 + MX6QDL_PAD_GPIO_6__I2C3_SDA 0x4001b8b1 + >; + }; + + pinctrl_pcie: pciegrp { + fsl,pins = < + MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0 + >; + }; + + pinctrl_pcie_reg: pciereggrp { + fsl,pins = < + MX6QDL_PAD_EIM_D19__GPIO3_IO19 0x1b0b0 + >; + }; + + pinctrl_pwm1: pwm1grp { + fsl,pins = < + MX6QDL_PAD_SD1_DAT3__PWM1_OUT 0x1b0b1 + >; + }; + + pinctrl_uart1: uart1grp { + fsl,pins = < + MX6QDL_PAD_CSI0_DAT10__UART1_TX_DATA 0x1b0b1 + MX6QDL_PAD_CSI0_DAT11__UART1_RX_DATA 0x1b0b1 + >; + }; + + pinctrl_uart5: uart5grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL1__UART5_TX_DATA 0x1b0b1 + MX6QDL_PAD_KEY_ROW1__UART5_RX_DATA 0x1b0b1 + MX6QDL_PAD_KEY_ROW4__UART5_CTS_B 0x1b0b1 + MX6QDL_PAD_KEY_COL4__UART5_RTS_B 0x1b0b1 + >; + }; + + pinctrl_usbotg: usbotggrp { + fsl,pins = < + MX6QDL_PAD_ENET_RX_ER__USB_OTG_ID 0x17059 + >; + }; + + pinctrl_usdhc2: usdhc2grp { + fsl,pins = < + MX6QDL_PAD_SD2_CMD__SD2_CMD 0x17059 + MX6QDL_PAD_SD2_CLK__SD2_CLK 0x10059 + MX6QDL_PAD_SD2_DAT0__SD2_DATA0 0x17059 + MX6QDL_PAD_SD2_DAT1__SD2_DATA1 0x17059 + MX6QDL_PAD_SD2_DAT2__SD2_DATA2 0x17059 + MX6QDL_PAD_SD2_DAT3__SD2_DATA3 0x17059 + MX6QDL_PAD_NANDF_D4__SD2_DATA4 0x17059 + MX6QDL_PAD_NANDF_D5__SD2_DATA5 0x17059 + MX6QDL_PAD_NANDF_D6__SD2_DATA6 0x17059 + MX6QDL_PAD_NANDF_D7__SD2_DATA7 0x17059 + >; + }; + + pinctrl_usdhc3: usdhc3grp { + fsl,pins = < + MX6QDL_PAD_SD3_CMD__SD3_CMD 0x17059 + MX6QDL_PAD_SD3_CLK__SD3_CLK 0x10059 + MX6QDL_PAD_SD3_DAT0__SD3_DATA0 0x17059 + MX6QDL_PAD_SD3_DAT1__SD3_DATA1 0x17059 + MX6QDL_PAD_SD3_DAT2__SD3_DATA2 0x17059 + MX6QDL_PAD_SD3_DAT3__SD3_DATA3 0x17059 + MX6QDL_PAD_SD3_DAT4__SD3_DATA4 0x17059 + MX6QDL_PAD_SD3_DAT5__SD3_DATA5 0x17059 + MX6QDL_PAD_SD3_DAT6__SD3_DATA6 0x17059 + MX6QDL_PAD_SD3_DAT7__SD3_DATA7 0x17059 + >; + }; + + pinctrl_usdhc4: usdhc4grp { + fsl,pins = < + MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059 + MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059 + MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059 + MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059 + MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059 + MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059 + MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059 + MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059 + MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059 + MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059 + >; + }; + }; + + gpio_leds { + pinctrl_gpio_leds: gpioledsgrp { + fsl,pins = < + MX6QDL_PAD_GPIO_2__GPIO1_IO02 0x1b0b0 + >; + }; + }; +}; + +&ldb { + status = "okay"; + + lvds-channel@1 { + fsl,data-mapping = "spwg"; + fsl,data-width = <18>; + status = "okay"; + + display-timings { + native-mode = <&timing0>; + timing0: hsd100pxn1 { + clock-frequency = <65000000>; + hactive = <1024>; + vactive = <768>; + hback-porch = <220>; + hfront-porch = <40>; + vback-porch = <21>; + vfront-porch = <7>; + hsync-len = <60>; + vsync-len = <10>; + }; + }; + }; +}; + +&pcie { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pcie>; + reset-gpio = <&gpio7 12 0>; + status = "okay"; +}; + +&pwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pwm1>; + status = "okay"; +}; + +&snvs_poweroff { + status = "okay"; +}; + +&ssi2 { + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart1>; + status = "okay"; +}; + +&uart5 { + compatible = "fsl,imx21-uart"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart5>; + status = "okay"; + + /* enable rts/cts usage on uart5 */ + fsl,uart-has-rtscts; +}; + +&usbh1 { + vbus-supply = <®_usb_h1_vbus>; + status = "okay"; +}; + +&usbotg { + vbus-supply = <®_usb_otg_vbus>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbotg>; + disable-over-current; + status = "okay"; +}; + +&usdhc2 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc2>; + bus-width = <8>; +/* cd-gpios = <&gpio2 2 0>; */ + wp-gpios = <&gpio2 3 0>; + no-1-8-v; + vmmc-supply = <&wlan_en_reg>; + non-removable; /* non-removable is not a variable, the fact it is */ + /* listed is all that is used by driver */ + cap-power-off-card; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + wlcore: wlcore@0 { + compatible = "ti,wl1835"; + reg = <2>; + interrupt-parent = <&gpio4>; + interrupts = <6 IRQ_TYPE_EDGE_RISING>; + }; +}; + +&usdhc3 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc3>; + bus-width = <8>; + cd-gpios = <&gpio2 0 0>; + wp-gpios = <&gpio2 1 0>; + status = "okay"; +}; + +&usdhc4 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc4>; + bus-width = <8>; + non-removable; + no-1-8-v; + status = "okay"; +}; diff --git a/arch/arm/boot/dts/imx6qdl-udoo.dtsi b/arch/arm/boot/dts/imx6qdl-udoo.dtsi index 1211da8..e8fc7d8 100644 --- a/arch/arm/boot/dts/imx6qdl-udoo.dtsi +++ b/arch/arm/boot/dts/imx6qdl-udoo.dtsi @@ -9,6 +9,8 @@ * */ +#include + / { chosen { stdout-path = &uart2; @@ -17,23 +19,6 @@ memory { reg = <0x10000000 0x40000000>; }; - - regulators { - compatible = "simple-bus"; - #address-cells = <1>; - #size-cells = <0>; - - reg_usb_h1_vbus: regulator@0 { - compatible = "regulator-fixed"; - reg = <0>; - regulator-name = "usb_h1_vbus"; - regulator-min-microvolt = <5000000>; - regulator-max-microvolt = <5000000>; - enable-active-high; - startup-delay-us = <2>; /* USB2415 requires a POR of 1 us minimum */ - gpio = <&gpio7 12 0>; - }; - }; }; &fec { @@ -55,8 +40,57 @@ status = "okay"; }; +&i2c3 { // CSI camera (CN11) and LVDS 7 inches touch panel (CN13) + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c3>; + status = "okay"; + ov5640_mipi: ov5640_mipi@3c { + compatible = "ovti,ov5640_mipi"; + reg = <0x3c>; + clocks = <&clks IMX6QDL_CLK_CKO>; + clock-names = "csi_mclk"; + pwn-gpios = <&gpio6 4 GPIO_ACTIVE_LOW>; + rst-gpios = <&gpio6 5 GPIO_ACTIVE_HIGH>; + csi_id = <0>; + mclk = <24000000>; + mclk_source = <0>; + }; +}; + &iomuxc { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hog>; + imx6q-udoo { + pinctrl_hog: hoggrp { + fsl,pins = < + // Internal GPIOs + MX6QDL_PAD_NANDF_D4__GPIO2_IO04 0x80000000 // 5v enable + MX6QDL_PAD_NANDF_CS0__GPIO6_IO11 0x80000000 // Vtt enable + + MX6QDL_PAD_DISP0_DAT5__GPIO4_IO26 0x80000000 // Debug UART (J18) + + MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000 // USB hub reset + MX6QDL_PAD_NANDF_CS2__CCM_CLKO2 0x130b0 // USB hub clock + MX6QDL_PAD_EIM_WAIT__GPIO5_IO00 0xb0b1 // USB OTG select + + MX6QDL_PAD_NANDF_D5__GPIO2_IO05 0x80000000 // SD card power + MX6QDL_PAD_SD3_DAT5__GPIO7_IO00 0x80000000 // SD card detect + + MX6QDL_PAD_GPIO_16__GPIO7_IO11 0xb0b1 // SAM3X OTG vbus_en + MX6QDL_PAD_SD4_DAT7__GPIO2_IO15 0x80000000 // SAM3X usb host + MX6QDL_PAD_GPIO_3__GPIO1_IO03 0x30b1 // Arduino pinout pin 12 + + MX6QDL_PAD_GPIO_2__GPIO1_IO02 0x80000000 // LVDS panel on (CN13) + MX6QDL_PAD_GPIO_4__GPIO1_IO04 0x80000000 // LVDS backlight on (CN13) + + MX6QDL_PAD_CSI0_DAT18__GPIO6_IO04 0x1f071 // CSI camera enable (CN11) + MX6QDL_PAD_CSI0_DAT19__GPIO6_IO05 0x1f071 // CSI camera reset (CN11) + MX6QDL_PAD_CSI0_MCLK__CCM_CLKO1 0x130b0 // CSI master clock (CN11) + >; + }; + pinctrl_enet: enetgrp { fsl,pins = < MX6QDL_PAD_RGMII_RXC__RGMII_RXC 0x1b0b0 @@ -85,6 +119,13 @@ >; }; + pinctrl_i2c3: i2c3grp { + fsl,pins = < + MX6QDL_PAD_GPIO_5__I2C3_SCL 0x4001b8b1 + MX6QDL_PAD_GPIO_6__I2C3_SDA 0x4001b8b1 + >; + }; + pinctrl_uart2: uart2grp { fsl,pins = < MX6QDL_PAD_EIM_D26__UART2_TX_DATA 0x1b0b1 @@ -92,6 +133,13 @@ >; }; + pinctrl_uart4: uart4grp { + fsl,pins = < + MX6QDL_PAD_KEY_COL0__UART4_TX_DATA 0x1b0b1 + MX6QDL_PAD_KEY_ROW0__UART4_RX_DATA 0x1b0b1 + >; + }; + pinctrl_usbh: usbhgrp { fsl,pins = < MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000 @@ -118,12 +166,26 @@ status = "okay"; }; -&usbh1 { +&uart4 { // iMX6-Arduino internal serial port - ttymxc3 pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_usbh>; - vbus-supply = <®_usb_h1_vbus>; - clocks = <&clks 201>; + pinctrl-0 = <&pinctrl_uart4>; + status = "okay"; +}; + +&usbh1 { status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + hub: usb2415@01 { + compatible = "generic-onboard-device"; + reg = <0x01>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbh>; + clocks = <&clks IMX6QDL_CLK_CKO>; + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + reset-duration-us = <2>; + }; }; &usdhc3 { @@ -132,3 +194,11 @@ non-removable; status = "okay"; }; + +&mipi_csi { + status = "okay"; + ipu_id = <0>; + csi_id = <0>; + v_channel = <0>; + lanes = <2>; +}; diff --git a/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi index ef7fa62..b5d0f58 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard-revb1.dtsi @@ -11,6 +11,24 @@ #include "imx6qdl-wandboard.dtsi" +/ { + rfkill { + compatible = "wand,imx6qdl-wandboard-rfkill"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + bluetooth-on = <&gpio3 13 0>; + bluetooth-wake = <&gpio3 14 0>; + bluetooth-host-wake = <&gpio3 15 0>; + + wifi-ref-on = <&gpio2 29 0>; + wifi-rst-n = <&gpio5 2 0>; + wifi-reg-on = <&gpio1 26 0>; + wifi-host-wake = <&gpio1 29 0>; + wifi-wake = <&gpio1 30 0>; + }; +}; + &iomuxc { pinctrl-0 = <&pinctrl_hog>; @@ -37,6 +55,5 @@ &usdhc2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usdhc2>; - non-removable; status = "okay"; }; diff --git a/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi b/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi index 8d893a7..b2097ef 100644 --- a/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi +++ b/arch/arm/boot/dts/imx6qdl-wandboard-revc1.dtsi @@ -11,6 +11,24 @@ #include "imx6qdl-wandboard.dtsi" +/ { + rfkill { + compatible = "wand,imx6qdl-wandboard-rfkill"; + pinctrl-names = "default"; + pinctrl-0 = <>; + + bluetooth-on = <&gpio5 21 0>; + bluetooth-wake = <&gpio5 30 0>; + bluetooth-host-wake = <&gpio5 20 0>; + + wifi-ref-on = <&gpio5 31 0>; /* Wifi Power Enable */ + wifi-rst-n = <&gpio6 0 0>; /* WIFI_ON reset */ + wifi-reg-on = <&gpio1 26 0>; /* WL_REG_ON */ + wifi-host-wake = <&gpio1 29 0>; /* WL_HOST_WAKE */ + wifi-wake = <&gpio1 30 0>; /* WL_WAKE */ + }; +}; + &iomuxc { pinctrl-0 = <&pinctrl_hog>; diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi index a70a193..2e2247a 100644 --- a/arch/arm/boot/dts/imx6qdl.dtsi +++ b/arch/arm/boot/dts/imx6qdl.dtsi @@ -147,6 +147,27 @@ }; }; + gpu_3d: gpu@00130000 { + compatible = "vivante,gc"; + reg = <0x00130000 0x4000>; + interrupts = <0 9 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks IMX6QDL_CLK_GPU3D_AXI>, + <&clks IMX6QDL_CLK_GPU3D_CORE>, + <&clks IMX6QDL_CLK_GPU3D_SHADER>; + clock-names = "bus", "core", "shader"; + power-domains = <&gpc 1>; + }; + + gpu_2d: gpu@00134000 { + compatible = "vivante,gc"; + reg = <0x00134000 0x4000>; + interrupts = <0 10 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks IMX6QDL_CLK_GPU2D_AXI>, + <&clks IMX6QDL_CLK_GPU2D_CORE>; + clock-names = "bus", "core"; + power-domains = <&gpc 1>; + }; + timer@00a00600 { compatible = "arm,cortex-a9-twd-timer"; reg = <0x00a00600 0x20>; diff --git b/arch/arm/boot/dts/imx6sl-evk-wl1835.dts b/arch/arm/boot/dts/imx6sl-evk-wl1835.dts new file mode 100644 index 0000000..e5853f3 --- /dev/null +++ b/arch/arm/boot/dts/imx6sl-evk-wl1835.dts @@ -0,0 +1,633 @@ +/* + * Copyright (C) 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. + */ + +/dts-v1/; + +#include +#include +#include "imx6sl.dtsi" + +/ { + model = "Freescale i.MX6 SoloLite EVK Board"; + compatible = "fsl,imx6sl-evk", "fsl,imx6sl"; + + memory { + reg = <0x80000000 0x40000000>; + }; + + backlight { + compatible = "pwm-backlight"; + pwms = <&pwm1 0 5000000>; + brightness-levels = <0 4 8 16 32 64 128 255>; + default-brightness-level = <6>; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_led>; + + user { + label = "debug"; + gpios = <&gpio3 20 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + }; + + regulators { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <0>; + + reg_usb_otg1_vbus: regulator@0 { + compatible = "regulator-fixed"; + reg = <0>; + regulator-name = "usb_otg1_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio4 0 0>; + enable-active-high; + vin-supply = <&swbst_reg>; + }; + + reg_usb_otg2_vbus: regulator@1 { + compatible = "regulator-fixed"; + reg = <1>; + regulator-name = "usb_otg2_vbus"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + gpio = <&gpio4 2 0>; + enable-active-high; + vin-supply = <&swbst_reg>; + }; + + reg_aud3v: regulator@2 { + compatible = "regulator-fixed"; + reg = <2>; + regulator-name = "wm8962-supply-3v15"; + regulator-min-microvolt = <3150000>; + regulator-max-microvolt = <3150000>; + regulator-boot-on; + }; + + reg_aud4v: regulator@3 { + compatible = "regulator-fixed"; + reg = <3>; + regulator-name = "wm8962-supply-4v2"; + regulator-min-microvolt = <4325000>; + regulator-max-microvolt = <4325000>; + regulator-boot-on; + }; + + reg_lcd_3v3: regulator@4 { + compatible = "regulator-fixed"; + reg = <4>; + regulator-name = "lcd-3v3"; + gpio = <&gpio4 3 0>; + enable-active-high; + }; + + wlan_en_reg: regulator@5 { + compatible = "regulator-fixed"; + regulator-name = "wlan-en-regulator"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + /* WLAN_EN GPIO for this board – Bank5, pin13 */ + gpio = <&gpio5 13 0>; + enable-active-high; + }; + }; + + sound { + compatible = "fsl,imx6sl-evk-wm8962", "fsl,imx-audio-wm8962"; + model = "wm8962-audio"; + ssi-controller = <&ssi2>; + audio-codec = <&codec>; + audio-routing = + "Headphone Jack", "HPOUTL", + "Headphone Jack", "HPOUTR", + "Ext Spk", "SPKOUTL", + "Ext Spk", "SPKOUTR", + "AMIC", "MICBIAS", + "IN3R", "AMIC"; + mux-int-port = <2>; + mux-ext-port = <3>; + }; + + kim { + compatible = "kim"; + nshutdown_gpio = <134>; // GPIO5_6 The wl8 driver expects gpio to be an integer, so gpio5_6 is (5-1)*32+6=134 + dev_name = "/dev/ttymxc3"; + flow_cntrl = <1>; + baud_rate = <3000000>; + }; + + btwilink { + compatible = "btwilink"; + }; +}; + +&audmux { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_audmux3>; + status = "okay"; +}; + +&ecspi1 { + fsl,spi-num-chipselects = <1>; + cs-gpios = <&gpio4 11 0>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ecspi1>; + status = "okay"; + + flash: m25p80@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "st,m25p32"; + spi-max-frequency = <20000000>; + reg = <0>; + }; +}; + +&fec { + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&pinctrl_fec>; + pinctrl-1 = <&pinctrl_fec_sleep>; + phy-mode = "rmii"; + status = "okay"; +}; + +&i2c1 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c1>; + status = "okay"; + + pmic: pfuze100@08 { + compatible = "fsl,pfuze100"; + reg = <0x08>; + + regulators { + sw1a_reg: sw1ab { + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1875000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <6250>; + }; + + sw1c_reg: sw1c { + regulator-min-microvolt = <300000>; + regulator-max-microvolt = <1875000>; + regulator-boot-on; + regulator-always-on; + regulator-ramp-delay = <6250>; + }; + + sw2_reg: sw2 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + sw3a_reg: sw3a { + regulator-min-microvolt = <400000>; + regulator-max-microvolt = <1975000>; + regulator-boot-on; + regulator-always-on; + }; + + sw3b_reg: sw3b { + regulator-min-microvolt = <400000>; + regulator-max-microvolt = <1975000>; + regulator-boot-on; + regulator-always-on; + }; + + sw4_reg: sw4 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3300000>; + }; + + swbst_reg: swbst { + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5150000>; + }; + + snvs_reg: vsnvs { + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <3000000>; + regulator-boot-on; + regulator-always-on; + }; + + vref_reg: vrefddr { + regulator-boot-on; + regulator-always-on; + }; + + vgen1_reg: vgen1 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1550000>; + regulator-always-on; + }; + + vgen2_reg: vgen2 { + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <1550000>; + }; + + vgen3_reg: vgen3 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + }; + + vgen4_reg: vgen4 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vgen5_reg: vgen5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + vgen6_reg: vgen6 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + }; + }; +}; + +&i2c2 { + clock-frequency = <100000>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_i2c2>; + status = "okay"; + + codec: wm8962@1a { + compatible = "wlf,wm8962"; + reg = <0x1a>; + clocks = <&clks IMX6SL_CLK_EXTERN_AUDIO>; + DCVDD-supply = <&vgen3_reg>; + DBVDD-supply = <®_aud3v>; + AVDD-supply = <&vgen3_reg>; + CPVDD-supply = <&vgen3_reg>; + MICVDD-supply = <®_aud3v>; + PLLVDD-supply = <&vgen3_reg>; + SPKVDD1-supply = <®_aud4v>; + SPKVDD2-supply = <®_aud4v>; + }; +}; + +&iomuxc { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_hog>; + + imx6sl-evk { + pinctrl_hog: hoggrp { + fsl,pins = < + MX6SL_PAD_KEY_ROW7__GPIO4_IO07 0x17059 + MX6SL_PAD_KEY_COL7__GPIO4_IO06 0x17059 + MX6SL_PAD_SD2_DAT7__GPIO5_IO00 0x17059 + MX6SL_PAD_SD2_DAT6__GPIO4_IO29 0x17059 + MX6SL_PAD_REF_CLK_32K__GPIO3_IO22 0x17059 + MX6SL_PAD_KEY_COL4__GPIO4_IO00 0x80000000 + MX6SL_PAD_KEY_COL5__GPIO4_IO02 0x80000000 + MX6SL_PAD_AUD_MCLK__AUDIO_CLK_OUT 0x4130b0 + MX6SL_PAD_SD1_DAT2__GPIO5_IO13 0x13059 // reserve two pins from sd1 for wl8 gpio, this is pulled low at reset for WL_EN + MX6SL_PAD_SD1_DAT1__GPIO5_IO08 0x13059 // this is for wl_irq which driver will configure as an input with a pull down + >; + }; + + pinctrl_audmux3: audmux3grp { + fsl,pins = < + MX6SL_PAD_AUD_RXD__AUD3_RXD 0x4130b0 + MX6SL_PAD_AUD_TXC__AUD3_TXC 0x4130b0 + MX6SL_PAD_AUD_TXD__AUD3_TXD 0x4110b0 + MX6SL_PAD_AUD_TXFS__AUD3_TXFS 0x4130b0 + >; + }; + + pinctrl_ecspi1: ecspi1grp { + fsl,pins = < + MX6SL_PAD_ECSPI1_MISO__ECSPI1_MISO 0x100b1 + MX6SL_PAD_ECSPI1_MOSI__ECSPI1_MOSI 0x100b1 + MX6SL_PAD_ECSPI1_SCLK__ECSPI1_SCLK 0x100b1 + MX6SL_PAD_ECSPI1_SS0__GPIO4_IO11 0x80000000 + >; + }; + + pinctrl_fec: fecgrp { + fsl,pins = < + MX6SL_PAD_FEC_MDC__FEC_MDC 0x1b0b0 + MX6SL_PAD_FEC_MDIO__FEC_MDIO 0x1b0b0 + MX6SL_PAD_FEC_CRS_DV__FEC_RX_DV 0x1b0b0 + MX6SL_PAD_FEC_RXD0__FEC_RX_DATA0 0x1b0b0 + MX6SL_PAD_FEC_RXD1__FEC_RX_DATA1 0x1b0b0 + MX6SL_PAD_FEC_TX_EN__FEC_TX_EN 0x1b0b0 + MX6SL_PAD_FEC_TXD0__FEC_TX_DATA0 0x1b0b0 + MX6SL_PAD_FEC_TXD1__FEC_TX_DATA1 0x1b0b0 + MX6SL_PAD_FEC_REF_CLK__FEC_REF_OUT 0x4001b0a8 + >; + }; + + pinctrl_fec_sleep: fecgrp-sleep { + fsl,pins = < + MX6SL_PAD_FEC_MDC__GPIO4_IO23 0x3080 + MX6SL_PAD_FEC_CRS_DV__GPIO4_IO25 0x3080 + MX6SL_PAD_FEC_RXD0__GPIO4_IO17 0x3080 + MX6SL_PAD_FEC_RXD1__GPIO4_IO18 0x3080 + MX6SL_PAD_FEC_TX_EN__GPIO4_IO22 0x3080 + MX6SL_PAD_FEC_TXD0__GPIO4_IO24 0x3080 + MX6SL_PAD_FEC_TXD1__GPIO4_IO16 0x3080 + MX6SL_PAD_FEC_REF_CLK__GPIO4_IO26 0x3080 + >; + }; + + pinctrl_i2c1: i2c1grp { + fsl,pins = < + MX6SL_PAD_I2C1_SCL__I2C1_SCL 0x4001b8b1 + MX6SL_PAD_I2C1_SDA__I2C1_SDA 0x4001b8b1 + >; + }; + + + pinctrl_i2c2: i2c2grp { + fsl,pins = < + MX6SL_PAD_I2C2_SCL__I2C2_SCL 0x4001b8b1 + MX6SL_PAD_I2C2_SDA__I2C2_SDA 0x4001b8b1 + >; + }; + + pinctrl_kpp: kppgrp { + fsl,pins = < + MX6SL_PAD_KEY_ROW0__KEY_ROW0 0x1b010 + MX6SL_PAD_KEY_ROW1__KEY_ROW1 0x1b010 + MX6SL_PAD_KEY_ROW2__KEY_ROW2 0x1b0b0 + MX6SL_PAD_KEY_COL0__KEY_COL0 0x110b0 + MX6SL_PAD_KEY_COL1__KEY_COL1 0x110b0 + MX6SL_PAD_KEY_COL2__KEY_COL2 0x110b0 + >; + }; + + pinctrl_lcd: lcdgrp { + fsl,pins = < + MX6SL_PAD_LCD_DAT0__LCD_DATA00 0x1b0b0 + MX6SL_PAD_LCD_DAT1__LCD_DATA01 0x1b0b0 + MX6SL_PAD_LCD_DAT2__LCD_DATA02 0x1b0b0 + MX6SL_PAD_LCD_DAT3__LCD_DATA03 0x1b0b0 + MX6SL_PAD_LCD_DAT4__LCD_DATA04 0x1b0b0 + MX6SL_PAD_LCD_DAT5__LCD_DATA05 0x1b0b0 + MX6SL_PAD_LCD_DAT6__LCD_DATA06 0x1b0b0 + MX6SL_PAD_LCD_DAT7__LCD_DATA07 0x1b0b0 + MX6SL_PAD_LCD_DAT8__LCD_DATA08 0x1b0b0 + MX6SL_PAD_LCD_DAT9__LCD_DATA09 0x1b0b0 + MX6SL_PAD_LCD_DAT10__LCD_DATA10 0x1b0b0 + MX6SL_PAD_LCD_DAT11__LCD_DATA11 0x1b0b0 + MX6SL_PAD_LCD_DAT12__LCD_DATA12 0x1b0b0 + MX6SL_PAD_LCD_DAT13__LCD_DATA13 0x1b0b0 + MX6SL_PAD_LCD_DAT14__LCD_DATA14 0x1b0b0 + MX6SL_PAD_LCD_DAT15__LCD_DATA15 0x1b0b0 + MX6SL_PAD_LCD_DAT16__LCD_DATA16 0x1b0b0 + MX6SL_PAD_LCD_DAT17__LCD_DATA17 0x1b0b0 + MX6SL_PAD_LCD_DAT18__LCD_DATA18 0x1b0b0 + MX6SL_PAD_LCD_DAT19__LCD_DATA19 0x1b0b0 + MX6SL_PAD_LCD_DAT20__LCD_DATA20 0x1b0b0 + MX6SL_PAD_LCD_DAT21__LCD_DATA21 0x1b0b0 + MX6SL_PAD_LCD_DAT22__LCD_DATA22 0x1b0b0 + MX6SL_PAD_LCD_DAT23__LCD_DATA23 0x1b0b0 + MX6SL_PAD_LCD_CLK__LCD_CLK 0x1b0b0 + MX6SL_PAD_LCD_ENABLE__LCD_ENABLE 0x1b0b0 + MX6SL_PAD_LCD_HSYNC__LCD_HSYNC 0x1b0b0 + MX6SL_PAD_LCD_VSYNC__LCD_VSYNC 0x1b0b0 + >; + }; + + pinctrl_led: ledgrp { + fsl,pins = < + MX6SL_PAD_HSIC_STROBE__GPIO3_IO20 0x17059 + >; + }; + + pinctrl_pwm1: pwmgrp { + fsl,pins = < + MX6SL_PAD_PWM1__PWM1_OUT 0x110b0 + >; + }; + + pinctrl_uart1: uart1grp { + fsl,pins = < + MX6SL_PAD_UART1_RXD__UART1_RX_DATA 0x1b0b1 + MX6SL_PAD_UART1_TXD__UART1_TX_DATA 0x1b0b1 + >; + }; + + pinctrl_uart4: uart4grp { + fsl,pins = < + MX6SL_PAD_SD1_DAT4__UART4_RX_DATA 0x13059 // used for HOST_HCI_RX + MX6SL_PAD_SD1_DAT5__UART4_TX_DATA 0x13059 // used for HOST_HCI_TX + MX6SL_PAD_SD1_DAT7__UART4_CTS_B 0x13059 // used for HOST_HCI_RTS, note reversed to TI nomenclature + MX6SL_PAD_SD1_DAT6__UART4_RTS_B 0x13059 // used for HOST_HCI_CTS + MX6SL_PAD_SD1_DAT3__GPIO5_IO06 0x13059 // used for BT_EN + >; + }; + + pinctrl_usbotg1: usbotg1grp { + fsl,pins = < + MX6SL_PAD_EPDC_PWRCOM__USB_OTG1_ID 0x17059 + >; + }; + + pinctrl_usdhc1: usdhc1grp { + fsl,pins = < + MX6SL_PAD_SD1_CMD__SD1_CMD 0x17059 + MX6SL_PAD_SD1_CLK__SD1_CLK 0x10059 + MX6SL_PAD_SD1_DAT0__SD1_DATA0 0x17059 + MX6SL_PAD_SD1_DAT1__SD1_DATA1 0x17059 + MX6SL_PAD_SD1_DAT2__SD1_DATA2 0x17059 + MX6SL_PAD_SD1_DAT3__SD1_DATA3 0x17059 + MX6SL_PAD_SD1_DAT4__SD1_DATA4 0x17059 + MX6SL_PAD_SD1_DAT5__SD1_DATA5 0x17059 + MX6SL_PAD_SD1_DAT6__SD1_DATA6 0x17059 + MX6SL_PAD_SD1_DAT7__SD1_DATA7 0x17059 + >; + }; + + pinctrl_usdhc2: usdhc2grp { + fsl,pins = < + MX6SL_PAD_SD2_CMD__SD2_CMD 0x17059 + MX6SL_PAD_SD2_CLK__SD2_CLK 0x10059 + MX6SL_PAD_SD2_DAT0__SD2_DATA0 0x17059 + MX6SL_PAD_SD2_DAT1__SD2_DATA1 0x17059 + MX6SL_PAD_SD2_DAT2__SD2_DATA2 0x17059 + MX6SL_PAD_SD2_DAT3__SD2_DATA3 0x17059 + >; + }; + + pinctrl_usdhc2_100mhz: usdhc2grp100mhz { + fsl,pins = < + MX6SL_PAD_SD2_CMD__SD2_CMD 0x170b9 + MX6SL_PAD_SD2_CLK__SD2_CLK 0x100b9 + MX6SL_PAD_SD2_DAT0__SD2_DATA0 0x170b9 + MX6SL_PAD_SD2_DAT1__SD2_DATA1 0x170b9 + MX6SL_PAD_SD2_DAT2__SD2_DATA2 0x170b9 + MX6SL_PAD_SD2_DAT3__SD2_DATA3 0x170b9 + >; + }; + + pinctrl_usdhc2_200mhz: usdhc2grp200mhz { + fsl,pins = < + MX6SL_PAD_SD2_CMD__SD2_CMD 0x170f9 + MX6SL_PAD_SD2_CLK__SD2_CLK 0x100f9 + MX6SL_PAD_SD2_DAT0__SD2_DATA0 0x170f9 + MX6SL_PAD_SD2_DAT1__SD2_DATA1 0x170f9 + MX6SL_PAD_SD2_DAT2__SD2_DATA2 0x170f9 + MX6SL_PAD_SD2_DAT3__SD2_DATA3 0x170f9 + >; + }; + + pinctrl_usdhc3: usdhc3grp { + fsl,pins = < + MX6SL_PAD_SD3_CMD__SD3_CMD 0x17059 + MX6SL_PAD_SD3_CLK__SD3_CLK 0x10059 + MX6SL_PAD_SD3_DAT0__SD3_DATA0 0x17059 + MX6SL_PAD_SD3_DAT1__SD3_DATA1 0x17059 + MX6SL_PAD_SD3_DAT2__SD3_DATA2 0x17059 + MX6SL_PAD_SD3_DAT3__SD3_DATA3 0x17059 + >; + }; + }; +}; + +&kpp { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_kpp>; + linux,keymap = < + MATRIX_KEY(0x0, 0x0, KEY_UP) /* ROW0, COL0 */ + MATRIX_KEY(0x0, 0x1, KEY_DOWN) /* ROW0, COL1 */ + MATRIX_KEY(0x0, 0x2, KEY_ENTER) /* ROW0, COL2 */ + MATRIX_KEY(0x1, 0x0, KEY_HOME) /* ROW1, COL0 */ + MATRIX_KEY(0x1, 0x1, KEY_RIGHT) /* ROW1, COL1 */ + MATRIX_KEY(0x1, 0x2, KEY_LEFT) /* ROW1, COL2 */ + MATRIX_KEY(0x2, 0x0, KEY_VOLUMEDOWN) /* ROW2, COL0 */ + MATRIX_KEY(0x2, 0x1, KEY_VOLUMEUP) /* ROW2, COL1 */ + >; + status = "okay"; +}; + +&lcdif { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_lcd>; + lcd-supply = <®_lcd_3v3>; + display = <&display0>; + status = "okay"; + + display0: display0 { + bits-per-pixel = <32>; + bus-width = <24>; + + display-timings { + native-mode = <&timing0>; + timing0: timing0 { + clock-frequency = <33500000>; + hactive = <800>; + vactive = <480>; + hback-porch = <89>; + hfront-porch = <164>; + vback-porch = <23>; + vfront-porch = <10>; + hsync-len = <10>; + vsync-len = <10>; + hsync-active = <0>; + vsync-active = <0>; + de-active = <1>; + pixelclk-active = <0>; + }; + }; + }; +}; + +&pwm1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pwm1>; + status = "okay"; +}; + +&snvs_poweroff { + status = "okay"; +}; + +&ssi2 { + status = "okay"; +}; + +&uart1 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart1>; + status = "okay"; +}; + +&uart4 { + compatible = "fsl,imx21-uart"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_uart4>; + status = "okay"; + + /* enable rts/cts usage on uart4 */ + fsl,uart-has-rtscts; +}; + +&usbotg1 { + vbus-supply = <®_usb_otg1_vbus>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usbotg1>; + disable-over-current; + status = "okay"; +}; + +&usbotg2 { + vbus-supply = <®_usb_otg2_vbus>; + dr_mode = "host"; + disable-over-current; + status = "okay"; +}; + +&usdhc2 { + pinctrl-names = "default", "state_100mhz", "state_200mhz"; + pinctrl-0 = <&pinctrl_usdhc2>; + pinctrl-1 = <&pinctrl_usdhc2_100mhz>; + pinctrl-2 = <&pinctrl_usdhc2_200mhz>; + cd-gpios = <&gpio5 0 0>; + wp-gpios = <&gpio4 29 0>; + status = "okay"; +}; + +&usdhc3 { + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usdhc3>; + keep-power-in-suspend; + enable-sdio-wakeup; + vmmc-supply = <&wlan_en_reg>; + non-removable; // non-removable is not a variable, the fact it is listed is all that is used by driver + cap-power-off-card; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + wlcore: wlcore@0 { + compatible = "ti,wl1835"; + reg = <2>; + interrupt-parent = <&gpio5>; + interrupts = <8 IRQ_TYPE_EDGE_RISING>; + }; +}; diff --git a/arch/arm/boot/dts/omap3-beagle-xm.dts b/arch/arm/boot/dts/omap3-beagle-xm.dts index 73f1e3a..e7512ed 100644 --- a/arch/arm/boot/dts/omap3-beagle-xm.dts +++ b/arch/arm/boot/dts/omap3-beagle-xm.dts @@ -146,6 +146,7 @@ }; etb@5401b000 { + status = "disabled"; compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0x5401b000 0x1000>; @@ -160,6 +161,7 @@ }; etm@54010000 { + status = "disabled"; compatible = "arm,coresight-etm3x", "arm,primecell"; reg = <0x54010000 0x1000>; @@ -205,6 +207,25 @@ >; }; + spi3_pins: pinmux_spi3_pins { + pinctrl-single,pins = < + 0x128 (PIN_INPUT | MUX_MODE1) /* sdmmc2_clk.mcspi3_clk gpio_130 */ + 0x12a (PIN_OUTPUT | MUX_MODE1) /* sdmmc2_cmd.mcspi3_simo gpio_131 */ + 0x12c (PIN_INPUT_PULLUP | MUX_MODE1) /* sdmmc2_dat0.mcspi3_somi gpio_132 */ + 0x130 (PIN_OUTPUT | MUX_MODE1) /* sdmmc2_dat2.mcspi3_cs1 gpio_134 */ + 0x132 (PIN_OUTPUT | MUX_MODE1) /* sdmmc2_dat3.mcspi3_cs0 gpio_135 */ + >; + }; + + spi4_pins: pinmux_spi4_pins { + pinctrl-single,pins = < + 0x15c (PIN_INPUT | MUX_MODE1) /* mcbsp1_clkr.mcspi4_clk gpio_156 */ + 0x160 (PIN_OUTPUT | MUX_MODE1) /* mcbsp1_dx.mcspi4_simo gpio_158 */ + 0x162 (PIN_INPUT_PULLUP | MUX_MODE1) /* mcbsp1_dr.mcspi4_somi gpio_159 */ + 0x166 (PIN_OUTPUT | MUX_MODE1) /* mcbsp1_fsx.mcspi4_cs0 gpio_161 */ + >; + }; + hsusb2_pins: pinmux_hsusb2_pins { pinctrl-single,pins = < OMAP3_CORE1_IOPAD(0x21d4, PIN_INPUT_PULLDOWN | MUX_MODE3) /* mcspi1_cs3.hsusb2_data2 */ @@ -279,7 +300,7 @@ }; twl_power: power { - compatible = "ti,twl4030-power-beagleboard-xm", "ti,twl4030-power-idle-osc-off"; + compatible = "ti,twl4030-power-reset"; ti,use_poweroff; }; }; @@ -310,6 +331,36 @@ status = "disabled"; }; +&mcspi3 { + pinctrl-names = "default"; + pinctrl-0 = <&spi3_pins>; + status = "okay"; + + spidev0: spi@0 { + compatible = "spidev"; + reg = <0>; + spi-max-frequency = <48000000>; + }; + + spidev1: spi@1 { + compatible = "spidev"; + reg = <1>; + spi-max-frequency = <48000000>; + }; +}; + +&mcspi4 { + pinctrl-names = "default"; + pinctrl-0 = <&spi4_pins>; + status = "okay"; + + spidev2: spi@0 { + compatible = "spidev"; + reg = <0>; + spi-max-frequency = <48000000>; + }; +}; + &twl_gpio { ti,use-leds; /* pullups: BIT(1) */ diff --git a/arch/arm/boot/dts/omap3-beagle.dts b/arch/arm/boot/dts/omap3-beagle.dts index 274c2c4..e31773b 100644 --- a/arch/arm/boot/dts/omap3-beagle.dts +++ b/arch/arm/boot/dts/omap3-beagle.dts @@ -141,6 +141,7 @@ }; etb@540000000 { + status = "disabled"; compatible = "arm,coresight-etb10", "arm,primecell"; reg = <0x5401b000 0x1000>; @@ -155,6 +156,7 @@ }; etm@54010000 { + status = "disabled"; compatible = "arm,coresight-etm3x", "arm,primecell"; reg = <0x54010000 0x1000>; @@ -271,9 +273,18 @@ codec { }; }; + + twl_power: power { + compatible = "ti,twl4030-power-reset"; + ti,use_poweroff; + }; }; }; +&i2c2 { + clock-frequency = <400000>; +}; + #include "twl4030.dtsi" #include "twl4030_omap3.dtsi" diff --git a/arch/arm/boot/dts/omap4-panda-a4.dts b/arch/arm/boot/dts/omap4-panda-a4.dts index 133f1b7..072f0a0 100644 --- a/arch/arm/boot/dts/omap4-panda-a4.dts +++ b/arch/arm/boot/dts/omap4-panda-a4.dts @@ -10,6 +10,18 @@ #include "omap443x.dtsi" #include "omap4-panda-common.dtsi" +&emif1 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; + +&emif2 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; + /* Pandaboard Rev A4+ have external pullups on SCL & SDA */ &dss_hdmi_pins { pinctrl-single,pins = < diff --git a/arch/arm/boot/dts/omap4-panda-common.dtsi b/arch/arm/boot/dts/omap4-panda-common.dtsi index 18d0966..d0b778c 100644 --- a/arch/arm/boot/dts/omap4-panda-common.dtsi +++ b/arch/arm/boot/dts/omap4-panda-common.dtsi @@ -460,16 +460,6 @@ }; }; -&emif1 { - cs1-used; - device-handle = <&elpida_ECB240ABACN>; -}; - -&emif2 { - cs1-used; - device-handle = <&elpida_ECB240ABACN>; -}; - &mcbsp1 { pinctrl-names = "default"; pinctrl-0 = <&mcbsp1_pins>; diff --git b/arch/arm/boot/dts/omap4-panda-es-b3.dts b/arch/arm/boot/dts/omap4-panda-es-b3.dts new file mode 100644 index 0000000..2f1dabc --- /dev/null +++ b/arch/arm/boot/dts/omap4-panda-es-b3.dts @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/ + * + * 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. + */ +/dts-v1/; + +#include "omap4460.dtsi" +#include "omap4-panda-common.dtsi" + +/ { + model = "TI OMAP4 PandaBoard-ES"; + compatible = "ti,omap4-panda-es", "ti,omap4-panda", "ti,omap4460", "ti,omap4430", "ti,omap4"; +}; + +/* Audio routing is differnet between PandaBoard4430 and PandaBoardES */ +&sound { + ti,model = "PandaBoardES"; + + /* Audio routing */ + ti,audio-routing = + "Headset Stereophone", "HSOL", + "Headset Stereophone", "HSOR", + "Ext Spk", "HFL", + "Ext Spk", "HFR", + "Line Out", "AUXL", + "Line Out", "AUXR", + "AFML", "Line In", + "AFMR", "Line In"; +}; + +/* PandaboardES has external pullups on SCL & SDA */ +&dss_hdmi_pins { + pinctrl-single,pins = < + 0x5a (PIN_INPUT_PULLUP | MUX_MODE0) /* hdmi_cec.hdmi_cec */ + 0x5c (PIN_INPUT | MUX_MODE0) /* hdmi_scl.hdmi_scl */ + 0x5e (PIN_INPUT | MUX_MODE0) /* hdmi_sda.hdmi_sda */ + >; +}; + +&omap4_pmx_core { + led_gpio_pins: gpio_led_pmx { + pinctrl-single,pins = < + 0xb6 (PIN_OUTPUT | MUX_MODE3) /* gpio_110 */ + >; + }; +}; + +&led_wkgpio_pins { + pinctrl-single,pins = < + 0x1c (PIN_OUTPUT | MUX_MODE3) /* gpio_wk8 */ + >; +}; + +&leds { + pinctrl-0 = < + &led_gpio_pins + &led_wkgpio_pins + >; + + heartbeat { + gpios = <&gpio4 14 GPIO_ACTIVE_HIGH>; + }; + mmc { + gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; + }; +}; + +&gpio1 { + ti,no-reset-on-init; +}; diff --git a/arch/arm/boot/dts/omap4-panda-es.dts b/arch/arm/boot/dts/omap4-panda-es.dts index 2f1dabc..206b523 100644 --- a/arch/arm/boot/dts/omap4-panda-es.dts +++ b/arch/arm/boot/dts/omap4-panda-es.dts @@ -15,6 +15,18 @@ compatible = "ti,omap4-panda-es", "ti,omap4-panda", "ti,omap4460", "ti,omap4430", "ti,omap4"; }; +&emif1 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; + +&emif2 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; + /* Audio routing is differnet between PandaBoard4430 and PandaBoardES */ &sound { ti,model = "PandaBoardES"; diff --git a/arch/arm/boot/dts/omap4-panda.dts b/arch/arm/boot/dts/omap4-panda.dts index a0e28b2..3ee41ef 100644 --- a/arch/arm/boot/dts/omap4-panda.dts +++ b/arch/arm/boot/dts/omap4-panda.dts @@ -14,3 +14,15 @@ model = "TI OMAP4 PandaBoard"; compatible = "ti,omap4-panda", "ti,omap4430", "ti,omap4"; }; + +&emif1 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; + +&emif2 { + cs1-used; + device-handle = <&elpida_ECB240ABACN>; + status = "ok"; +}; diff --git a/arch/arm/boot/dts/omap4-sdp.dts b/arch/arm/boot/dts/omap4-sdp.dts index f0bdc41..184f98e 100644 --- a/arch/arm/boot/dts/omap4-sdp.dts +++ b/arch/arm/boot/dts/omap4-sdp.dts @@ -501,11 +501,13 @@ &emif1 { cs1-used; device-handle = <&elpida_ECB240ABACN>; + status = "ok"; }; &emif2 { cs1-used; device-handle = <&elpida_ECB240ABACN>; + status = "ok"; }; &keypad { diff --git a/arch/arm/boot/dts/omap4.dtsi b/arch/arm/boot/dts/omap4.dtsi index 5a206c1..6fa8b82 100644 --- a/arch/arm/boot/dts/omap4.dtsi +++ b/arch/arm/boot/dts/omap4.dtsi @@ -681,6 +681,7 @@ hw-caps-read-idle-ctrl; hw-caps-ll-interface; hw-caps-temp-alert; + status = "disabled"; }; emif2: emif@4d000000 { @@ -693,6 +694,7 @@ hw-caps-read-idle-ctrl; hw-caps-ll-interface; hw-caps-temp-alert; + status = "disabled"; }; ocp2scp@4a0ad000 { diff --git a/arch/arm/mach-davinci/board-mityomapl138.c b/arch/arm/mach-davinci/board-mityomapl138.c index 8cfbfe0..867ca34 100644 --- a/arch/arm/mach-davinci/board-mityomapl138.c +++ b/arch/arm/mach-davinci/board-mityomapl138.c @@ -115,13 +115,14 @@ static void mityomapl138_cpufreq_init(const char *partnum) static void mityomapl138_cpufreq_init(const char *partnum) { } #endif -static void read_factory_config(struct memory_accessor *a, void *context) +static void read_factory_config(struct nvmem_device *nvmem, void *context) { int ret; const char *partnum = NULL; struct davinci_soc_info *soc_info = &davinci_soc_info; - ret = a->read(a, (char *)&factory_config, 0, sizeof(factory_config)); + ret = nvmem_device_read(nvmem, 0, sizeof(factory_config), + &factory_config); if (ret != sizeof(struct factory_config)) { pr_warn("Read Factory Config Failed: %d\n", ret); goto bad_config; diff --git a/arch/arm/mach-davinci/common.c b/arch/arm/mach-davinci/common.c index a794f6d..f55ef2e 100644 --- a/arch/arm/mach-davinci/common.c +++ b/arch/arm/mach-davinci/common.c @@ -28,13 +28,13 @@ EXPORT_SYMBOL(davinci_soc_info); void __iomem *davinci_intc_base; int davinci_intc_type; -void davinci_get_mac_addr(struct memory_accessor *mem_acc, void *context) +void davinci_get_mac_addr(struct nvmem_device *nvmem, void *context) { char *mac_addr = davinci_soc_info.emac_pdata->mac_addr; off_t offset = (off_t)context; /* Read MAC addr from EEPROM */ - if (mem_acc->read(mem_acc, mac_addr, offset, ETH_ALEN) == ETH_ALEN) + if (nvmem_device_read(nvmem, offset, ETH_ALEN, mac_addr) == ETH_ALEN) pr_info("Read MAC addr from EEPROM: %pM\n", mac_addr); } diff --git a/arch/arm/mach-imx/devices/Kconfig b/arch/arm/mach-imx/devices/Kconfig index 3a55298..aa16f7e 100644 --- a/arch/arm/mach-imx/devices/Kconfig +++ b/arch/arm/mach-imx/devices/Kconfig @@ -72,3 +72,9 @@ config IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX config IMX_HAVE_PLATFORM_SPI_IMX bool + +config WAND_RFKILL + tristate "Wandboard RF Kill support" + depends on SOC_IMX6Q + default m + select RFKILL diff --git a/arch/arm/mach-imx/devices/Makefile b/arch/arm/mach-imx/devices/Makefile index e5cf587..eee36d9 100644 --- a/arch/arm/mach-imx/devices/Makefile +++ b/arch/arm/mach-imx/devices/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_IMX_HAVE_PLATFORM_MXC_W1) += platform-mxc_w1.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SDHCI_ESDHC_IMX) += platform-sdhci-esdhc-imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_SPI_IMX) += platform-spi_imx.o obj-$(CONFIG_IMX_HAVE_PLATFORM_MX2_EMMA) += platform-mx2-emma.o +obj-$(CONFIG_WAND_RFKILL) += wand-rfkill.o diff --git b/arch/arm/mach-imx/devices/wand-rfkill.c b/arch/arm/mach-imx/devices/wand-rfkill.c new file mode 100644 index 0000000..da7ef9f --- /dev/null +++ b/arch/arm/mach-imx/devices/wand-rfkill.c @@ -0,0 +1,290 @@ +/* + * arch/arm/mach-imx/devices/wand-rfkill.c + * + * Copyright (C) 2013 Vladimir Ermakov + * + * based on net/rfkill/rfkill-gpio.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct wand_rfkill_data { + struct rfkill *rfkill_dev; + int shutdown_gpio; + const char *shutdown_name; +}; + +static int wand_rfkill_set_block(void *data, bool blocked) +{ + struct wand_rfkill_data *rfkill = data; + + pr_debug("wandboard-rfkill: set block %d\n", blocked); + + if (blocked) { + if (gpio_is_valid(rfkill->shutdown_gpio)) + gpio_direction_output(rfkill->shutdown_gpio, 0); + } else { + if (gpio_is_valid(rfkill->shutdown_gpio)) + gpio_direction_output(rfkill->shutdown_gpio, 1); + } + + return 0; +} + +static const struct rfkill_ops wand_rfkill_ops = { + .set_block = wand_rfkill_set_block, +}; + +static int wand_rfkill_wifi_probe(struct device *dev, + struct device_node *np, + struct wand_rfkill_data *rfkill) +{ + int ret; + int wl_ref_on, wl_rst_n, wl_reg_on, wl_wake, wl_host_wake; + + wl_ref_on = of_get_named_gpio(np, "wifi-ref-on", 0); + wl_rst_n = of_get_named_gpio(np, "wifi-rst-n", 0); + wl_reg_on = of_get_named_gpio(np, "wifi-reg-on", 0); + wl_wake = of_get_named_gpio(np, "wifi-wake", 0); + wl_host_wake = of_get_named_gpio(np, "wifi-host-wake", 0); + + if (!gpio_is_valid(wl_rst_n) || !gpio_is_valid(wl_ref_on) || + !gpio_is_valid(wl_reg_on) || !gpio_is_valid(wl_wake) || + !gpio_is_valid(wl_host_wake)) { + + dev_err(dev, "incorrect wifi gpios (%d %d %d %d %d)\n", + wl_rst_n, wl_ref_on, wl_reg_on, wl_wake, wl_host_wake); + return -EINVAL; + } + + dev_info(dev, "initialize wifi chip\n"); + + gpio_request(wl_rst_n, "wl_rst_n"); + gpio_direction_output(wl_rst_n, 0); + msleep(11); + gpio_set_value(wl_rst_n, 1); + + gpio_request(wl_ref_on, "wl_ref_on"); + gpio_direction_output(wl_ref_on, 1); + + gpio_request(wl_reg_on, "wl_reg_on"); + gpio_direction_output(wl_reg_on, 1); + + gpio_request(wl_wake, "wl_wake"); + gpio_direction_output(wl_wake, 1); + + gpio_request(wl_host_wake, "wl_host_wake"); + gpio_direction_input(wl_host_wake); + + rfkill->shutdown_name = "wifi_shutdown"; + rfkill->shutdown_gpio = wl_wake; + + rfkill->rfkill_dev = rfkill_alloc("wifi-rfkill", dev, RFKILL_TYPE_WLAN, + &wand_rfkill_ops, rfkill); + if (!rfkill->rfkill_dev) { + ret = -ENOMEM; + goto wifi_fail_free_gpio; + } + + ret = rfkill_register(rfkill->rfkill_dev); + if (ret < 0) + goto wifi_fail_unregister; + + dev_info(dev, "wifi-rfkill registered.\n"); + + return 0; + +wifi_fail_unregister: + rfkill_destroy(rfkill->rfkill_dev); +wifi_fail_free_gpio: + if (gpio_is_valid(wl_rst_n)) gpio_free(wl_rst_n); + if (gpio_is_valid(wl_ref_on)) gpio_free(wl_ref_on); + if (gpio_is_valid(wl_reg_on)) gpio_free(wl_reg_on); + if (gpio_is_valid(wl_wake)) gpio_free(wl_wake); + if (gpio_is_valid(wl_host_wake)) gpio_free(wl_host_wake); + + return ret; +} + +static int wand_rfkill_bt_probe(struct device *dev, + struct device_node *np, + struct wand_rfkill_data *rfkill) +{ + int ret; + int bt_on, bt_wake, bt_host_wake; + + bt_on = of_get_named_gpio(np, "bluetooth-on", 0); + bt_wake = of_get_named_gpio(np, "bluetooth-wake", 0); + bt_host_wake = of_get_named_gpio(np, "bluetooth-host-wake", 0); + + if (!gpio_is_valid(bt_on) || !gpio_is_valid(bt_wake) || + !gpio_is_valid(bt_host_wake)) { + + dev_err(dev, "incorrect bt gpios (%d %d %d)\n", + bt_on, bt_wake, bt_host_wake); + return -EINVAL; + } + + dev_info(dev, "initialize bluetooth chip\n"); + + gpio_request(bt_on, "bt_on"); + gpio_direction_output(bt_on, 0); + msleep(11); + gpio_set_value(bt_on, 1); + + gpio_request(bt_wake, "bt_wake"); + gpio_direction_output(bt_wake, 1); + + gpio_request(bt_host_wake, "bt_host_wake"); + gpio_direction_input(bt_host_wake); + + rfkill->shutdown_name = "bluetooth_shutdown"; + rfkill->shutdown_gpio = bt_wake; + + rfkill->rfkill_dev = rfkill_alloc("bluetooth-rfkill", dev, RFKILL_TYPE_BLUETOOTH, + &wand_rfkill_ops, rfkill); + if (!rfkill->rfkill_dev) { + ret = -ENOMEM; + goto bt_fail_free_gpio; + } + + ret = rfkill_register(rfkill->rfkill_dev); + if (ret < 0) + goto bt_fail_unregister; + + dev_info(dev, "bluetooth-rfkill registered.\n"); + + return 0; + +bt_fail_unregister: + rfkill_destroy(rfkill->rfkill_dev); +bt_fail_free_gpio: + if (gpio_is_valid(bt_on)) gpio_free(bt_on); + if (gpio_is_valid(bt_wake)) gpio_free(bt_wake); + if (gpio_is_valid(bt_host_wake)) gpio_free(bt_host_wake); + + return ret; +} + +static int wand_rfkill_probe(struct platform_device *pdev) +{ + struct wand_rfkill_data *rfkill; + struct pinctrl *pinctrl; + int ret; + + dev_info(&pdev->dev, "Wandboard rfkill initialization\n"); + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "no device tree node\n"); + return -ENODEV; + } + + rfkill = kzalloc(sizeof(*rfkill) * 2, GFP_KERNEL); + if (!rfkill) + return -ENOMEM; + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + int ret = PTR_ERR(pinctrl); + dev_err(&pdev->dev, "failed to get default pinctrl: %d\n", ret); + return ret; + } + + /* setup WiFi */ + ret = wand_rfkill_wifi_probe(&pdev->dev, pdev->dev.of_node, &rfkill[0]); + if (ret < 0) + goto fail_free_rfkill; + + /* setup bluetooth */ + ret = wand_rfkill_bt_probe(&pdev->dev, pdev->dev.of_node, &rfkill[1]); + if (ret < 0) + goto fail_unregister_wifi; + + platform_set_drvdata(pdev, rfkill); + + return 0; + +fail_unregister_wifi: + if (rfkill[1].rfkill_dev) { + rfkill_unregister(rfkill[1].rfkill_dev); + rfkill_destroy(rfkill[1].rfkill_dev); + } + + /* TODO free gpio */ + +fail_free_rfkill: + kfree(rfkill); + + return ret; +} + +static int wand_rfkill_remove(struct platform_device *pdev) +{ + struct wand_rfkill_data *rfkill = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "Module unloading\n"); + + if (!rfkill) + return 0; + + /* WiFi */ + if (gpio_is_valid(rfkill[0].shutdown_gpio)) + gpio_free(rfkill[0].shutdown_gpio); + + rfkill_unregister(rfkill[0].rfkill_dev); + rfkill_destroy(rfkill[0].rfkill_dev); + + /* Bt */ + if (gpio_is_valid(rfkill[1].shutdown_gpio)) + gpio_free(rfkill[1].shutdown_gpio); + + rfkill_unregister(rfkill[1].rfkill_dev); + rfkill_destroy(rfkill[1].rfkill_dev); + + kfree(rfkill); + + return 0; +} + +static struct of_device_id wand_rfkill_match[] = { + { .compatible = "wand,imx6q-wandboard-rfkill", }, + { .compatible = "wand,imx6dl-wandboard-rfkill", }, + { .compatible = "wand,imx6qdl-wandboard-rfkill", }, + {} +}; + +static struct platform_driver wand_rfkill_driver = { + .driver = { + .name = "wandboard-rfkill", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(wand_rfkill_match), + }, + .probe = wand_rfkill_probe, + .remove = wand_rfkill_remove +}; + +module_platform_driver(wand_rfkill_driver); + +MODULE_AUTHOR("Vladimir Ermakov "); +MODULE_DESCRIPTION("Wandboard rfkill driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-omap2/omap_device.c b/arch/arm/mach-omap2/omap_device.c index 72ebc4c..bb2326b 100644 --- a/arch/arm/mach-omap2/omap_device.c +++ b/arch/arm/mach-omap2/omap_device.c @@ -122,8 +122,8 @@ static int omap_device_build_from_dt(struct platform_device *pdev) struct omap_device *od; struct omap_hwmod *oh; struct device_node *node = pdev->dev.of_node; - const char *oh_name; - int oh_cnt, i, ret = 0; + const char *oh_name, *rst_name; + int oh_cnt, dstr_cnt, i, ret = 0; bool device_active = false; oh_cnt = of_property_count_strings(node, "ti,hwmods"); @@ -174,6 +174,26 @@ static int omap_device_build_from_dt(struct platform_device *pdev) omap_device_enable(pdev); pm_runtime_set_active(&pdev->dev); } + dstr_cnt = + of_property_count_strings(node, "ti,deassert-hard-reset"); + if (dstr_cnt > 0) { + for (i = 0; i < dstr_cnt; i += 2) { + of_property_read_string_index( + node, "ti,deassert-hard-reset", i, + &oh_name); + of_property_read_string_index( + node, "ti,deassert-hard-reset", i+1, + &rst_name); + oh = omap_hwmod_lookup(oh_name); + if (!oh) { + dev_warn(&pdev->dev, + "Cannot parse deassert property for '%s'\n", + oh_name); + break; + } + omap_hwmod_deassert_hardreset(oh, rst_name); + } + } odbfd_exit1: kfree(hwmods); @@ -190,11 +210,30 @@ static int _omap_device_notifier_call(struct notifier_block *nb, { struct platform_device *pdev = to_platform_device(dev); struct omap_device *od; + int i, err; switch (event) { case BUS_NOTIFY_DEL_DEVICE: - if (pdev->archdata.od) - omap_device_delete(pdev->archdata.od); + od = to_omap_device(pdev); + if (!od) + break; + + for (i = 0; i < od->hwmods_cnt; i++) { + /* shutdown hwmods */ + omap_hwmod_shutdown(od->hwmods[i]); + /* we don't remove clocks cause there's no API to do so */ + /* no harm done, since they will not be created next time */ + } + omap_device_delete(od); + break; + case BUS_NOTIFY_UNBOUND_DRIVER: + od = to_omap_device(pdev); + if (od && (od->_state == OMAP_DEVICE_STATE_ENABLED)) { + dev_info(dev, "enabled after unload, idling\n"); + err = omap_device_idle(pdev); + if (err) + dev_err(dev, "failed to idle\n"); + } break; case BUS_NOTIFY_ADD_DEVICE: if (pdev->dev.of_node) @@ -757,6 +796,8 @@ int omap_device_idle(struct platform_device *pdev) struct omap_device *od; od = to_omap_device(pdev); + if (!od) + return 0; if (od->_state != OMAP_DEVICE_STATE_ENABLED) { dev_warn(&pdev->dev, diff --git a/arch/arm/mach-omap2/pdata-quirks.c b/arch/arm/mach-omap2/pdata-quirks.c index 5814477..339b859 100644 --- a/arch/arm/mach-omap2/pdata-quirks.c +++ b/arch/arm/mach-omap2/pdata-quirks.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include "common.h" #include "common-board-devices.h" @@ -427,6 +429,24 @@ void omap_auxdata_legacy_init(struct device *dev) dev->platform_data = &twl_gpio_auxdata; } +/* Dual mode timer PWM callbacks platdata */ +#if IS_ENABLED(CONFIG_OMAP_DM_TIMER) +struct pwm_omap_dmtimer_pdata pwm_dmtimer_pdata = { + .request_by_node = omap_dm_timer_request_by_node, + .free = omap_dm_timer_free, + .enable = omap_dm_timer_enable, + .disable = omap_dm_timer_disable, + .get_fclk = omap_dm_timer_get_fclk, + .start = omap_dm_timer_start, + .stop = omap_dm_timer_stop, + .set_load = omap_dm_timer_set_load, + .set_match = omap_dm_timer_set_match, + .set_pwm = omap_dm_timer_set_pwm, + .set_prescaler = omap_dm_timer_set_prescaler, + .write_counter = omap_dm_timer_write_counter, +}; +#endif + /* * Few boards still need auxdata populated before we populate * the dev entries in of_platform_populate(). @@ -480,6 +500,9 @@ static struct of_dev_auxdata omap_auxdata_lookup[] __initdata = { OF_DEV_AUXDATA("ti,am4372-wkup-m3", 0x44d00000, "44d00000.wkup_m3", &wkup_m3_data), #endif +#if IS_ENABLED(CONFIG_OMAP_DM_TIMER) + OF_DEV_AUXDATA("ti,omap-dmtimer-pwm", 0, NULL, &pwm_dmtimer_pdata), +#endif #if defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_SOC_OMAP5) OF_DEV_AUXDATA("ti,omap4-iommu", 0x4a066000, "4a066000.mmu", &omap4_iommu_pdata), diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 469dc37..ed0fb08 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -86,6 +86,20 @@ config GPIO_SYSFS Kernel drivers may also request that a particular GPIO be exported to userspace; this can be useful when debugging. +config GPIO_OF_HELPER + bool "GPIO OF helper device (EXPERIMENTAL)" + depends on OF_GPIO + help + Say Y here to add an GPIO OF helper driver + + Allows you specify a GPIO helper based on OF + which allows simple export of GPIO functionality + in user-space. + + Features include, value set/get, direction control, + interrupt/value change poll support, event counting + and others. + config GPIO_GENERIC tristate diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 986dbd8..113912b 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_GPIOLIB) += gpiolib-legacy.o obj-$(CONFIG_OF_GPIO) += gpiolib-of.o obj-$(CONFIG_GPIO_SYSFS) += gpiolib-sysfs.o obj-$(CONFIG_GPIO_ACPI) += gpiolib-acpi.o +obj-$(CONFIG_GPIO_OF_HELPER) += gpio-of-helper.o # Device drivers. Generally keep list sorted alphabetically obj-$(CONFIG_GPIO_GENERIC) += gpio-generic.o diff --git b/drivers/gpio/gpio-of-helper.c b/drivers/gpio/gpio-of-helper.c new file mode 100644 index 0000000..a9f260b --- /dev/null +++ b/drivers/gpio/gpio-of-helper.c @@ -0,0 +1,429 @@ +/* + * GPIO OF based helper + * + * A simple DT based driver to provide access to GPIO functionality + * to user-space via sysfs. + * + * Copyright (C) 2013 Pantelis Antoniou + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* fwd decl. */ +struct gpio_of_helper_info; + +enum gpio_type { + GPIO_TYPE_INPUT = 0, + GPIO_TYPE_OUTPUT = 1, +}; + +struct gpio_of_entry { + int id; + struct gpio_of_helper_info *info; + struct device_node *node; + enum gpio_type type; + int gpio; + enum of_gpio_flags gpio_flags; + int irq; + const char *name; + atomic64_t counter; + unsigned int count_flags; +#define COUNT_RISING_EDGE (1 << 0) +#define COUNT_FALLING_EDGE (1 << 1) +}; + +struct gpio_of_helper_info { + struct platform_device *pdev; + struct idr idr; +}; + +static const struct of_device_id gpio_of_helper_of_match[] = { + { + .compatible = "gpio-of-helper", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, gpio_of_helper_of_match); + +static ssize_t gpio_of_helper_show_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gpio_of_helper_info *info = platform_get_drvdata(pdev); + struct gpio_of_entry *entry; + char *p, *e; + int id, n; + + p = buf; + e = p + PAGE_SIZE; + n = 0; + idr_for_each_entry(&info->idr, entry, id) { + switch (entry->type) { + case GPIO_TYPE_INPUT: + n = snprintf(p, e - p, "%2d %-24s %3d %-3s %llu\n", + entry->id, entry->name, entry->gpio, "IN", + (unsigned long long) + atomic64_read(&entry->counter)); + break; + case GPIO_TYPE_OUTPUT: + n = snprintf(p, e - p, "%2d %-24s %3d %-3s\n", + entry->id, entry->name, entry->gpio, "OUT"); + break; + } + p += n; + } + + return p - buf; +} + +static DEVICE_ATTR(status, S_IRUGO, + gpio_of_helper_show_status, NULL); + +static irqreturn_t gpio_of_helper_handler(int irq, void *ptr) +{ + struct gpio_of_entry *entry = ptr; + + /* caution - low speed interfaces only! */ + atomic64_inc(&entry->counter); + + return IRQ_HANDLED; +} + +static struct gpio_of_entry * +gpio_of_entry_create(struct gpio_of_helper_info *info, + struct device_node *node) +{ + struct platform_device *pdev = info->pdev; + struct device *dev = &pdev->dev; + struct gpio_of_entry *entry; + int err, gpio, irq; + unsigned int req_flags, count_flags, irq_flags; + enum gpio_type type; + enum of_gpio_flags gpio_flags; + const char *name; + + /* get the type of the node first */ + if (of_property_read_bool(node, "input")) + type = GPIO_TYPE_INPUT; + else if (of_property_read_bool(node, "output")) + type = GPIO_TYPE_OUTPUT; + else { + dev_err(dev, "Not valid gpio node type\n"); + err = -EINVAL; + goto err_bad_node; + } + + /* get the name */ + err = of_property_read_string(node, "gpio-name", &name); + if (err != 0) { + dev_err(dev, "Failed to get name property\n"); + goto err_bad_node; + } + + err = of_get_named_gpio_flags(node, "gpio", 0, &gpio_flags); + if (IS_ERR_VALUE(err)) { + dev_err(dev, "Failed to get gpio property of '%s'\n", name); + goto err_bad_node; + } + gpio = err; + + req_flags = 0; + count_flags = 0; + + /* set the request flags */ + switch (type) { + case GPIO_TYPE_INPUT: + req_flags = GPIOF_DIR_IN | GPIOF_EXPORT; + if (of_property_read_bool(node, "count-falling-edge")) + count_flags |= COUNT_FALLING_EDGE; + if (of_property_read_bool(node, "count-rising-edge")) + count_flags |= COUNT_RISING_EDGE; + break; + case GPIO_TYPE_OUTPUT: + req_flags = GPIOF_DIR_OUT | GPIOF_EXPORT; + if (of_property_read_bool(node, "init-high")) + req_flags |= GPIOF_OUT_INIT_HIGH; + else if (of_property_read_bool(node, "init-low")) + req_flags |= GPIOF_OUT_INIT_LOW; + break; + } + if (of_property_read_bool(node, "dir-changeable")) + req_flags |= GPIOF_EXPORT_CHANGEABLE; + + /* request the gpio */ + err = devm_gpio_request_one(dev, gpio, req_flags, name); + if (err != 0) { + dev_err(dev, "Failed to request gpio '%s'\n", name); + goto err_bad_node; + } + + irq = -1; + irq_flags = 0; + + /* counter mode requested - need an interrupt */ + if (count_flags != 0) { + irq = gpio_to_irq(gpio); + if (IS_ERR_VALUE(irq)) { + dev_err(dev, "Failed to request gpio '%s'\n", name); + goto err_bad_node; + } + + if (count_flags & COUNT_RISING_EDGE) + irq_flags |= IRQF_TRIGGER_RISING; + if (count_flags & COUNT_FALLING_EDGE) + irq_flags |= IRQF_TRIGGER_FALLING; + } + +// if (!idr_pre_get(&info->idr, GFP_KERNEL)) { +// dev_err(dev, "Failed on idr_pre_get of '%s'\n", name); +// err = -ENOMEM; +// goto err_no_mem; +// } + + idr_preload(GFP_KERNEL); + + entry = devm_kzalloc(dev, sizeof(*entry), GFP_KERNEL); + if (entry == NULL) { + dev_err(dev, "Failed to allocate gpio entry of '%s'\n", name); + err = -ENOMEM; + goto err_no_mem; + } + + entry->id = -1; + entry->info = info; + entry->node = of_node_get(node); /* get node reference */ + entry->type = type; + entry->gpio = gpio; + entry->gpio_flags = gpio_flags; + entry->irq = irq; + entry->name = name; + + /* interrupt enable is last thing done */ + if (irq >= 0) { + atomic64_set(&entry->counter, 0); + entry->count_flags = count_flags; + err = devm_request_irq(dev, irq, gpio_of_helper_handler, + irq_flags, name, entry); + if (err != 0) { + dev_err(dev, "Failed to request irq of '%s'\n", name); + goto err_no_irq; + } + } + + /* all done; insert */ +// err = idr_get_new(&info->idr, entry, &entry->id); +// if (IS_ERR_VALUE(err)) { +// dev_err(dev, "Failed to idr_get_new of '%s'\n", name); +// goto err_fail_idr; +// } + + err = idr_alloc(&info->idr, entry, 0, 0, GFP_NOWAIT); + if (err >= 0) + entry->id = err; + + idr_preload_end(); + + if (err < 0) { + dev_err(dev, "Failed to idr_get_new of '%s'\n", name); + goto err_fail_idr; + } + + dev_info(dev, "Allocated GPIO id=%d\n", entry->id); + + return entry; + +err_fail_idr: + /* nothing to do */ +err_no_irq: + /* release node ref */ + of_node_put(node); + /* nothing else needs to be done, devres handles it */ +err_no_mem: +err_bad_node: + return ERR_PTR(err); +} + +static int gpio_of_entry_destroy(struct gpio_of_entry *entry) +{ + struct gpio_of_helper_info *info = entry->info; + struct platform_device *pdev = info->pdev; + struct device *dev = &pdev->dev; + + dev_info(dev, "Destroying GPIO id=%d\n", entry->id); + + /* remove from the IDR */ + idr_remove(&info->idr, entry->id); + + /* remove node ref */ + of_node_put(entry->node); + + /* free gpio */ + devm_gpio_free(dev, entry->gpio); + + /* gree irq */ + if (entry->irq >= 0) + devm_free_irq(dev, entry->irq, entry); + + /* and free */ + devm_kfree(dev, entry); + + return 0; +} + +static int gpio_of_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_of_helper_info *info; + struct gpio_of_entry *entry; + struct device_node *pnode = pdev->dev.of_node; + struct device_node *cnode; + struct pinctrl *pinctrl; + int err; + + /* we only support OF */ + if (pnode == NULL) { + dev_err(&pdev->dev, "No platform of_node!\n"); + return -ENODEV; + } + + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + /* special handling for probe defer */ + if (PTR_ERR(pinctrl) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + dev_warn(&pdev->dev, + "pins are not configured from the driver\n"); + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (info == NULL) { + dev_err(&pdev->dev, "Failed to allocate info\n"); + err = -ENOMEM; + goto err_no_mem; + } + platform_set_drvdata(pdev, info); + info->pdev = pdev; + + idr_init(&info->idr); + + err = device_create_file(dev, &dev_attr_status); + if (err != 0) { + dev_err(dev, "Failed to create status sysfs attribute\n"); + goto err_no_sysfs; + } + + for_each_child_of_node(pnode, cnode) { + + entry = gpio_of_entry_create(info, cnode); + if (IS_ERR_OR_NULL(entry)) { + dev_err(dev, "Failed to create gpio entry\n"); + err = PTR_ERR(entry); + goto err_fail_entry; + } + } + + dev_info(&pdev->dev, "ready\n"); + + return 0; +err_fail_entry: + device_remove_file(&pdev->dev, &dev_attr_status); +err_no_sysfs: +err_no_mem: + return err; +} + +static int gpio_of_helper_remove(struct platform_device *pdev) +{ + struct gpio_of_helper_info *info = platform_get_drvdata(pdev); + struct gpio_of_entry *entry; + int id; + + dev_info(&pdev->dev, "removing\n"); + + device_remove_file(&pdev->dev, &dev_attr_status); + + id = 0; + idr_for_each_entry(&info->idr, entry, id) { + /* destroy each and every one */ + gpio_of_entry_destroy(entry); + } + + return 0; +} + +#ifdef CONFIG_PM +//#ifdef CONFIG_PM_RUNTIME +static int gpio_of_helper_runtime_suspend(struct device *dev) +{ + /* place holder */ + return 0; +} + +static int gpio_of_helper_runtime_resume(struct device *dev) +{ + /* place holder */ + return 0; +} +//#endif /* CONFIG_PM_RUNTIME */ + +static struct dev_pm_ops gpio_of_helper_pm_ops = { + SET_RUNTIME_PM_OPS(gpio_of_helper_runtime_suspend, + gpio_of_helper_runtime_resume, NULL) +}; +#define GPIO_OF_HELPER_PM_OPS (&gpio_of_helper_pm_ops) +#else +#define GPIO_OF_HELPER_PM_OPS NULL +#endif /* CONFIG_PM */ + +struct platform_driver gpio_of_helper_driver = { + .probe = gpio_of_helper_probe, + .remove = gpio_of_helper_remove, + .driver = { + .name = "gpio-of-helper", + .owner = THIS_MODULE, + .pm = GPIO_OF_HELPER_PM_OPS, + .of_match_table = gpio_of_helper_of_match, + }, +}; + +module_platform_driver(gpio_of_helper_driver); + +MODULE_AUTHOR("Pantelis Antoniou "); +MODULE_DESCRIPTION("GPIO OF Helper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-of-helper"); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index c4bf9a1..b02ac62 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -266,3 +266,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig" source "drivers/gpu/drm/imx/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" + +source "drivers/gpu/drm/etnaviv/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1e9ff4c..f858aa2 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -75,3 +75,4 @@ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ +obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ diff --git b/drivers/gpu/drm/etnaviv/Kconfig b/drivers/gpu/drm/etnaviv/Kconfig new file mode 100644 index 0000000..2cde7a5 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/Kconfig @@ -0,0 +1,20 @@ + +config DRM_ETNAVIV + tristate "ETNAVIV (DRM support for Vivante GPU IP cores)" + depends on DRM + depends on ARCH_MXC || ARCH_DOVE + select SHMEM + select TMPFS + select IOMMU_API + select IOMMU_SUPPORT + select WANT_DEV_COREDUMP + help + DRM driver for Vivante GPUs. + +config DRM_ETNAVIV_REGISTER_LOGGING + bool "enable ETNAVIV register logging" + depends on DRM_ETNAVIV + help + Compile in support for logging register reads/writes in a format + that can be parsed by envytools demsm tool. If enabled, register + logging can be switched on via etnaviv.reglog=y module param. diff --git b/drivers/gpu/drm/etnaviv/Makefile b/drivers/gpu/drm/etnaviv/Makefile new file mode 100644 index 0000000..1086e98 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/Makefile @@ -0,0 +1,14 @@ +etnaviv-y := \ + etnaviv_buffer.o \ + etnaviv_cmd_parser.o \ + etnaviv_drv.o \ + etnaviv_dump.o \ + etnaviv_gem_prime.o \ + etnaviv_gem_submit.o \ + etnaviv_gem.o \ + etnaviv_gpu.o \ + etnaviv_iommu_v2.o \ + etnaviv_iommu.o \ + etnaviv_mmu.o + +obj-$(CONFIG_DRM_ETNAVIV) += etnaviv.o diff --git b/drivers/gpu/drm/etnaviv/cmdstream.xml.h b/drivers/gpu/drm/etnaviv/cmdstream.xml.h new file mode 100644 index 0000000..8c44ba9 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/cmdstream.xml.h @@ -0,0 +1,218 @@ +#ifndef CMDSTREAM_XML +#define CMDSTREAM_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://0x04.net/cgit/index.cgi/rules-ng-ng +git clone git://0x04.net/rules-ng-ng + +The rules-ng-ng source files this header was generated from are: +- cmdstream.xml ( 12589 bytes, from 2014-02-17 14:57:56) +- common.xml ( 18437 bytes, from 2015-03-25 11:27:41) + +Copyright (C) 2014 +*/ + + +#define FE_OPCODE_LOAD_STATE 0x00000001 +#define FE_OPCODE_END 0x00000002 +#define FE_OPCODE_NOP 0x00000003 +#define FE_OPCODE_DRAW_2D 0x00000004 +#define FE_OPCODE_DRAW_PRIMITIVES 0x00000005 +#define FE_OPCODE_DRAW_INDEXED_PRIMITIVES 0x00000006 +#define FE_OPCODE_WAIT 0x00000007 +#define FE_OPCODE_LINK 0x00000008 +#define FE_OPCODE_STALL 0x00000009 +#define FE_OPCODE_CALL 0x0000000a +#define FE_OPCODE_RETURN 0x0000000b +#define FE_OPCODE_CHIP_SELECT 0x0000000d +#define PRIMITIVE_TYPE_POINTS 0x00000001 +#define PRIMITIVE_TYPE_LINES 0x00000002 +#define PRIMITIVE_TYPE_LINE_STRIP 0x00000003 +#define PRIMITIVE_TYPE_TRIANGLES 0x00000004 +#define PRIMITIVE_TYPE_TRIANGLE_STRIP 0x00000005 +#define PRIMITIVE_TYPE_TRIANGLE_FAN 0x00000006 +#define PRIMITIVE_TYPE_LINE_LOOP 0x00000007 +#define PRIMITIVE_TYPE_QUADS 0x00000008 +#define VIV_FE_LOAD_STATE 0x00000000 + +#define VIV_FE_LOAD_STATE_HEADER 0x00000000 +#define VIV_FE_LOAD_STATE_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_LOAD_STATE_HEADER_OP__SHIFT 27 +#define VIV_FE_LOAD_STATE_HEADER_OP_LOAD_STATE 0x08000000 +#define VIV_FE_LOAD_STATE_HEADER_FIXP 0x04000000 +#define VIV_FE_LOAD_STATE_HEADER_COUNT__MASK 0x03ff0000 +#define VIV_FE_LOAD_STATE_HEADER_COUNT__SHIFT 16 +#define VIV_FE_LOAD_STATE_HEADER_COUNT(x) (((x) << VIV_FE_LOAD_STATE_HEADER_COUNT__SHIFT) & VIV_FE_LOAD_STATE_HEADER_COUNT__MASK) +#define VIV_FE_LOAD_STATE_HEADER_OFFSET__MASK 0x0000ffff +#define VIV_FE_LOAD_STATE_HEADER_OFFSET__SHIFT 0 +#define VIV_FE_LOAD_STATE_HEADER_OFFSET(x) (((x) << VIV_FE_LOAD_STATE_HEADER_OFFSET__SHIFT) & VIV_FE_LOAD_STATE_HEADER_OFFSET__MASK) +#define VIV_FE_LOAD_STATE_HEADER_OFFSET__SHR 2 + +#define VIV_FE_END 0x00000000 + +#define VIV_FE_END_HEADER 0x00000000 +#define VIV_FE_END_HEADER_EVENT_ID__MASK 0x0000001f +#define VIV_FE_END_HEADER_EVENT_ID__SHIFT 0 +#define VIV_FE_END_HEADER_EVENT_ID(x) (((x) << VIV_FE_END_HEADER_EVENT_ID__SHIFT) & VIV_FE_END_HEADER_EVENT_ID__MASK) +#define VIV_FE_END_HEADER_EVENT_ENABLE 0x00000100 +#define VIV_FE_END_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_END_HEADER_OP__SHIFT 27 +#define VIV_FE_END_HEADER_OP_END 0x10000000 + +#define VIV_FE_NOP 0x00000000 + +#define VIV_FE_NOP_HEADER 0x00000000 +#define VIV_FE_NOP_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_NOP_HEADER_OP__SHIFT 27 +#define VIV_FE_NOP_HEADER_OP_NOP 0x18000000 + +#define VIV_FE_DRAW_2D 0x00000000 + +#define VIV_FE_DRAW_2D_HEADER 0x00000000 +#define VIV_FE_DRAW_2D_HEADER_COUNT__MASK 0x0000ff00 +#define VIV_FE_DRAW_2D_HEADER_COUNT__SHIFT 8 +#define VIV_FE_DRAW_2D_HEADER_COUNT(x) (((x) << VIV_FE_DRAW_2D_HEADER_COUNT__SHIFT) & VIV_FE_DRAW_2D_HEADER_COUNT__MASK) +#define VIV_FE_DRAW_2D_HEADER_DATA_COUNT__MASK 0x07ff0000 +#define VIV_FE_DRAW_2D_HEADER_DATA_COUNT__SHIFT 16 +#define VIV_FE_DRAW_2D_HEADER_DATA_COUNT(x) (((x) << VIV_FE_DRAW_2D_HEADER_DATA_COUNT__SHIFT) & VIV_FE_DRAW_2D_HEADER_DATA_COUNT__MASK) +#define VIV_FE_DRAW_2D_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_DRAW_2D_HEADER_OP__SHIFT 27 +#define VIV_FE_DRAW_2D_HEADER_OP_DRAW_2D 0x20000000 + +#define VIV_FE_DRAW_2D_TOP_LEFT 0x00000008 +#define VIV_FE_DRAW_2D_TOP_LEFT_X__MASK 0x0000ffff +#define VIV_FE_DRAW_2D_TOP_LEFT_X__SHIFT 0 +#define VIV_FE_DRAW_2D_TOP_LEFT_X(x) (((x) << VIV_FE_DRAW_2D_TOP_LEFT_X__SHIFT) & VIV_FE_DRAW_2D_TOP_LEFT_X__MASK) +#define VIV_FE_DRAW_2D_TOP_LEFT_Y__MASK 0xffff0000 +#define VIV_FE_DRAW_2D_TOP_LEFT_Y__SHIFT 16 +#define VIV_FE_DRAW_2D_TOP_LEFT_Y(x) (((x) << VIV_FE_DRAW_2D_TOP_LEFT_Y__SHIFT) & VIV_FE_DRAW_2D_TOP_LEFT_Y__MASK) + +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT 0x0000000c +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_X__MASK 0x0000ffff +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_X__SHIFT 0 +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_X(x) (((x) << VIV_FE_DRAW_2D_BOTTOM_RIGHT_X__SHIFT) & VIV_FE_DRAW_2D_BOTTOM_RIGHT_X__MASK) +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_Y__MASK 0xffff0000 +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_Y__SHIFT 16 +#define VIV_FE_DRAW_2D_BOTTOM_RIGHT_Y(x) (((x) << VIV_FE_DRAW_2D_BOTTOM_RIGHT_Y__SHIFT) & VIV_FE_DRAW_2D_BOTTOM_RIGHT_Y__MASK) + +#define VIV_FE_DRAW_PRIMITIVES 0x00000000 + +#define VIV_FE_DRAW_PRIMITIVES_HEADER 0x00000000 +#define VIV_FE_DRAW_PRIMITIVES_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_DRAW_PRIMITIVES_HEADER_OP__SHIFT 27 +#define VIV_FE_DRAW_PRIMITIVES_HEADER_OP_DRAW_PRIMITIVES 0x28000000 + +#define VIV_FE_DRAW_PRIMITIVES_COMMAND 0x00000004 +#define VIV_FE_DRAW_PRIMITIVES_COMMAND_TYPE__MASK 0x000000ff +#define VIV_FE_DRAW_PRIMITIVES_COMMAND_TYPE__SHIFT 0 +#define VIV_FE_DRAW_PRIMITIVES_COMMAND_TYPE(x) (((x) << VIV_FE_DRAW_PRIMITIVES_COMMAND_TYPE__SHIFT) & VIV_FE_DRAW_PRIMITIVES_COMMAND_TYPE__MASK) + +#define VIV_FE_DRAW_PRIMITIVES_START 0x00000008 + +#define VIV_FE_DRAW_PRIMITIVES_COUNT 0x0000000c + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES 0x00000000 + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_HEADER 0x00000000 +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_HEADER_OP__SHIFT 27 +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_HEADER_OP_DRAW_INDEXED_PRIMITIVES 0x30000000 + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND 0x00000004 +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND_TYPE__MASK 0x000000ff +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND_TYPE__SHIFT 0 +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND_TYPE(x) (((x) << VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND_TYPE__SHIFT) & VIV_FE_DRAW_INDEXED_PRIMITIVES_COMMAND_TYPE__MASK) + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_START 0x00000008 + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_COUNT 0x0000000c + +#define VIV_FE_DRAW_INDEXED_PRIMITIVES_OFFSET 0x00000010 + +#define VIV_FE_WAIT 0x00000000 + +#define VIV_FE_WAIT_HEADER 0x00000000 +#define VIV_FE_WAIT_HEADER_DELAY__MASK 0x0000ffff +#define VIV_FE_WAIT_HEADER_DELAY__SHIFT 0 +#define VIV_FE_WAIT_HEADER_DELAY(x) (((x) << VIV_FE_WAIT_HEADER_DELAY__SHIFT) & VIV_FE_WAIT_HEADER_DELAY__MASK) +#define VIV_FE_WAIT_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_WAIT_HEADER_OP__SHIFT 27 +#define VIV_FE_WAIT_HEADER_OP_WAIT 0x38000000 + +#define VIV_FE_LINK 0x00000000 + +#define VIV_FE_LINK_HEADER 0x00000000 +#define VIV_FE_LINK_HEADER_PREFETCH__MASK 0x0000ffff +#define VIV_FE_LINK_HEADER_PREFETCH__SHIFT 0 +#define VIV_FE_LINK_HEADER_PREFETCH(x) (((x) << VIV_FE_LINK_HEADER_PREFETCH__SHIFT) & VIV_FE_LINK_HEADER_PREFETCH__MASK) +#define VIV_FE_LINK_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_LINK_HEADER_OP__SHIFT 27 +#define VIV_FE_LINK_HEADER_OP_LINK 0x40000000 + +#define VIV_FE_LINK_ADDRESS 0x00000004 + +#define VIV_FE_STALL 0x00000000 + +#define VIV_FE_STALL_HEADER 0x00000000 +#define VIV_FE_STALL_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_STALL_HEADER_OP__SHIFT 27 +#define VIV_FE_STALL_HEADER_OP_STALL 0x48000000 + +#define VIV_FE_STALL_TOKEN 0x00000004 +#define VIV_FE_STALL_TOKEN_FROM__MASK 0x0000001f +#define VIV_FE_STALL_TOKEN_FROM__SHIFT 0 +#define VIV_FE_STALL_TOKEN_FROM(x) (((x) << VIV_FE_STALL_TOKEN_FROM__SHIFT) & VIV_FE_STALL_TOKEN_FROM__MASK) +#define VIV_FE_STALL_TOKEN_TO__MASK 0x00001f00 +#define VIV_FE_STALL_TOKEN_TO__SHIFT 8 +#define VIV_FE_STALL_TOKEN_TO(x) (((x) << VIV_FE_STALL_TOKEN_TO__SHIFT) & VIV_FE_STALL_TOKEN_TO__MASK) + +#define VIV_FE_CALL 0x00000000 + +#define VIV_FE_CALL_HEADER 0x00000000 +#define VIV_FE_CALL_HEADER_PREFETCH__MASK 0x0000ffff +#define VIV_FE_CALL_HEADER_PREFETCH__SHIFT 0 +#define VIV_FE_CALL_HEADER_PREFETCH(x) (((x) << VIV_FE_CALL_HEADER_PREFETCH__SHIFT) & VIV_FE_CALL_HEADER_PREFETCH__MASK) +#define VIV_FE_CALL_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_CALL_HEADER_OP__SHIFT 27 +#define VIV_FE_CALL_HEADER_OP_CALL 0x50000000 + +#define VIV_FE_CALL_ADDRESS 0x00000004 + +#define VIV_FE_CALL_RETURN_PREFETCH 0x00000008 + +#define VIV_FE_CALL_RETURN_ADDRESS 0x0000000c + +#define VIV_FE_RETURN 0x00000000 + +#define VIV_FE_RETURN_HEADER 0x00000000 +#define VIV_FE_RETURN_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_RETURN_HEADER_OP__SHIFT 27 +#define VIV_FE_RETURN_HEADER_OP_RETURN 0x58000000 + +#define VIV_FE_CHIP_SELECT 0x00000000 + +#define VIV_FE_CHIP_SELECT_HEADER 0x00000000 +#define VIV_FE_CHIP_SELECT_HEADER_OP__MASK 0xf8000000 +#define VIV_FE_CHIP_SELECT_HEADER_OP__SHIFT 27 +#define VIV_FE_CHIP_SELECT_HEADER_OP_CHIP_SELECT 0x68000000 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP15 0x00008000 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP14 0x00004000 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP13 0x00002000 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP12 0x00001000 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP11 0x00000800 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP10 0x00000400 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP9 0x00000200 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP8 0x00000100 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP7 0x00000080 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP6 0x00000040 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP5 0x00000020 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP4 0x00000010 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP3 0x00000008 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP2 0x00000004 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP1 0x00000002 +#define VIV_FE_CHIP_SELECT_HEADER_ENABLE_CHIP0 0x00000001 + + +#endif /* CMDSTREAM_XML */ diff --git b/drivers/gpu/drm/etnaviv/common.xml.h b/drivers/gpu/drm/etnaviv/common.xml.h new file mode 100644 index 0000000..e881482 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/common.xml.h @@ -0,0 +1,292 @@ +#ifndef COMMON_XML +#define COMMON_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://0x04.net/cgit/index.cgi/rules-ng-ng +git clone git://0x04.net/rules-ng-ng + +The rules-ng-ng source files this header was generated from are: +- state_hi.xml ( 24309 bytes, from 2015-12-12 09:02:53) +- common.xml ( 18379 bytes, from 2015-12-12 09:02:53) + +Copyright (C) 2015 +*/ + + +#define PIPE_ID_PIPE_3D 0x00000000 +#define PIPE_ID_PIPE_2D 0x00000001 +#define SYNC_RECIPIENT_FE 0x00000001 +#define SYNC_RECIPIENT_RA 0x00000005 +#define SYNC_RECIPIENT_PE 0x00000007 +#define SYNC_RECIPIENT_DE 0x0000000b +#define SYNC_RECIPIENT_VG 0x0000000f +#define SYNC_RECIPIENT_TESSELATOR 0x00000010 +#define SYNC_RECIPIENT_VG2 0x00000011 +#define SYNC_RECIPIENT_TESSELATOR2 0x00000012 +#define SYNC_RECIPIENT_VG3 0x00000013 +#define SYNC_RECIPIENT_TESSELATOR3 0x00000014 +#define ENDIAN_MODE_NO_SWAP 0x00000000 +#define ENDIAN_MODE_SWAP_16 0x00000001 +#define ENDIAN_MODE_SWAP_32 0x00000002 +#define chipModel_GC200 0x00000200 +#define chipModel_GC300 0x00000300 +#define chipModel_GC320 0x00000320 +#define chipModel_GC328 0x00000328 +#define chipModel_GC350 0x00000350 +#define chipModel_GC355 0x00000355 +#define chipModel_GC400 0x00000400 +#define chipModel_GC410 0x00000410 +#define chipModel_GC420 0x00000420 +#define chipModel_GC428 0x00000428 +#define chipModel_GC450 0x00000450 +#define chipModel_GC500 0x00000500 +#define chipModel_GC520 0x00000520 +#define chipModel_GC530 0x00000530 +#define chipModel_GC600 0x00000600 +#define chipModel_GC700 0x00000700 +#define chipModel_GC800 0x00000800 +#define chipModel_GC860 0x00000860 +#define chipModel_GC880 0x00000880 +#define chipModel_GC1000 0x00001000 +#define chipModel_GC1500 0x00001500 +#define chipModel_GC2000 0x00002000 +#define chipModel_GC2100 0x00002100 +#define chipModel_GC2200 0x00002200 +#define chipModel_GC2500 0x00002500 +#define chipModel_GC3000 0x00003000 +#define chipModel_GC4000 0x00004000 +#define chipModel_GC5000 0x00005000 +#define chipModel_GC5200 0x00005200 +#define chipModel_GC6400 0x00006400 +#define RGBA_BITS_R 0x00000001 +#define RGBA_BITS_G 0x00000002 +#define RGBA_BITS_B 0x00000004 +#define RGBA_BITS_A 0x00000008 +#define chipFeatures_FAST_CLEAR 0x00000001 +#define chipFeatures_SPECIAL_ANTI_ALIASING 0x00000002 +#define chipFeatures_PIPE_3D 0x00000004 +#define chipFeatures_DXT_TEXTURE_COMPRESSION 0x00000008 +#define chipFeatures_DEBUG_MODE 0x00000010 +#define chipFeatures_Z_COMPRESSION 0x00000020 +#define chipFeatures_YUV420_SCALER 0x00000040 +#define chipFeatures_MSAA 0x00000080 +#define chipFeatures_DC 0x00000100 +#define chipFeatures_PIPE_2D 0x00000200 +#define chipFeatures_ETC1_TEXTURE_COMPRESSION 0x00000400 +#define chipFeatures_FAST_SCALER 0x00000800 +#define chipFeatures_HIGH_DYNAMIC_RANGE 0x00001000 +#define chipFeatures_YUV420_TILER 0x00002000 +#define chipFeatures_MODULE_CG 0x00004000 +#define chipFeatures_MIN_AREA 0x00008000 +#define chipFeatures_NO_EARLY_Z 0x00010000 +#define chipFeatures_NO_422_TEXTURE 0x00020000 +#define chipFeatures_BUFFER_INTERLEAVING 0x00040000 +#define chipFeatures_BYTE_WRITE_2D 0x00080000 +#define chipFeatures_NO_SCALER 0x00100000 +#define chipFeatures_YUY2_AVERAGING 0x00200000 +#define chipFeatures_HALF_PE_CACHE 0x00400000 +#define chipFeatures_HALF_TX_CACHE 0x00800000 +#define chipFeatures_YUY2_RENDER_TARGET 0x01000000 +#define chipFeatures_MEM32 0x02000000 +#define chipFeatures_PIPE_VG 0x04000000 +#define chipFeatures_VGTS 0x08000000 +#define chipFeatures_FE20 0x10000000 +#define chipFeatures_BYTE_WRITE_3D 0x20000000 +#define chipFeatures_RS_YUV_TARGET 0x40000000 +#define chipFeatures_32_BIT_INDICES 0x80000000 +#define chipMinorFeatures0_FLIP_Y 0x00000001 +#define chipMinorFeatures0_DUAL_RETURN_BUS 0x00000002 +#define chipMinorFeatures0_ENDIANNESS_CONFIG 0x00000004 +#define chipMinorFeatures0_TEXTURE_8K 0x00000008 +#define chipMinorFeatures0_CORRECT_TEXTURE_CONVERTER 0x00000010 +#define chipMinorFeatures0_SPECIAL_MSAA_LOD 0x00000020 +#define chipMinorFeatures0_FAST_CLEAR_FLUSH 0x00000040 +#define chipMinorFeatures0_2DPE20 0x00000080 +#define chipMinorFeatures0_CORRECT_AUTO_DISABLE 0x00000100 +#define chipMinorFeatures0_RENDERTARGET_8K 0x00000200 +#define chipMinorFeatures0_2BITPERTILE 0x00000400 +#define chipMinorFeatures0_SEPARATE_TILE_STATUS_WHEN_INTERLEAVED 0x00000800 +#define chipMinorFeatures0_SUPER_TILED 0x00001000 +#define chipMinorFeatures0_VG_20 0x00002000 +#define chipMinorFeatures0_TS_EXTENDED_COMMANDS 0x00004000 +#define chipMinorFeatures0_COMPRESSION_FIFO_FIXED 0x00008000 +#define chipMinorFeatures0_HAS_SIGN_FLOOR_CEIL 0x00010000 +#define chipMinorFeatures0_VG_FILTER 0x00020000 +#define chipMinorFeatures0_VG_21 0x00040000 +#define chipMinorFeatures0_SHADER_HAS_W 0x00080000 +#define chipMinorFeatures0_HAS_SQRT_TRIG 0x00100000 +#define chipMinorFeatures0_MORE_MINOR_FEATURES 0x00200000 +#define chipMinorFeatures0_MC20 0x00400000 +#define chipMinorFeatures0_MSAA_SIDEBAND 0x00800000 +#define chipMinorFeatures0_BUG_FIXES0 0x01000000 +#define chipMinorFeatures0_VAA 0x02000000 +#define chipMinorFeatures0_BYPASS_IN_MSAA 0x04000000 +#define chipMinorFeatures0_HZ 0x08000000 +#define chipMinorFeatures0_NEW_TEXTURE 0x10000000 +#define chipMinorFeatures0_2D_A8_TARGET 0x20000000 +#define chipMinorFeatures0_CORRECT_STENCIL 0x40000000 +#define chipMinorFeatures0_ENHANCE_VR 0x80000000 +#define chipMinorFeatures1_RSUV_SWIZZLE 0x00000001 +#define chipMinorFeatures1_V2_COMPRESSION 0x00000002 +#define chipMinorFeatures1_VG_DOUBLE_BUFFER 0x00000004 +#define chipMinorFeatures1_EXTRA_EVENT_STATES 0x00000008 +#define chipMinorFeatures1_NO_STRIPING_NEEDED 0x00000010 +#define chipMinorFeatures1_TEXTURE_STRIDE 0x00000020 +#define chipMinorFeatures1_BUG_FIXES3 0x00000040 +#define chipMinorFeatures1_AUTO_DISABLE 0x00000080 +#define chipMinorFeatures1_AUTO_RESTART_TS 0x00000100 +#define chipMinorFeatures1_DISABLE_PE_GATING 0x00000200 +#define chipMinorFeatures1_L2_WINDOWING 0x00000400 +#define chipMinorFeatures1_HALF_FLOAT 0x00000800 +#define chipMinorFeatures1_PIXEL_DITHER 0x00001000 +#define chipMinorFeatures1_TWO_STENCIL_REFERENCE 0x00002000 +#define chipMinorFeatures1_EXTENDED_PIXEL_FORMAT 0x00004000 +#define chipMinorFeatures1_CORRECT_MIN_MAX_DEPTH 0x00008000 +#define chipMinorFeatures1_2D_DITHER 0x00010000 +#define chipMinorFeatures1_BUG_FIXES5 0x00020000 +#define chipMinorFeatures1_NEW_2D 0x00040000 +#define chipMinorFeatures1_NEW_FP 0x00080000 +#define chipMinorFeatures1_TEXTURE_HALIGN 0x00100000 +#define chipMinorFeatures1_NON_POWER_OF_TWO 0x00200000 +#define chipMinorFeatures1_LINEAR_TEXTURE_SUPPORT 0x00400000 +#define chipMinorFeatures1_HALTI0 0x00800000 +#define chipMinorFeatures1_CORRECT_OVERFLOW_VG 0x01000000 +#define chipMinorFeatures1_NEGATIVE_LOG_FIX 0x02000000 +#define chipMinorFeatures1_RESOLVE_OFFSET 0x04000000 +#define chipMinorFeatures1_OK_TO_GATE_AXI_CLOCK 0x08000000 +#define chipMinorFeatures1_MMU_VERSION 0x10000000 +#define chipMinorFeatures1_WIDE_LINE 0x20000000 +#define chipMinorFeatures1_BUG_FIXES6 0x40000000 +#define chipMinorFeatures1_FC_FLUSH_STALL 0x80000000 +#define chipMinorFeatures2_LINE_LOOP 0x00000001 +#define chipMinorFeatures2_LOGIC_OP 0x00000002 +#define chipMinorFeatures2_UNK2 0x00000004 +#define chipMinorFeatures2_SUPERTILED_TEXTURE 0x00000008 +#define chipMinorFeatures2_UNK4 0x00000010 +#define chipMinorFeatures2_RECT_PRIMITIVE 0x00000020 +#define chipMinorFeatures2_COMPOSITION 0x00000040 +#define chipMinorFeatures2_CORRECT_AUTO_DISABLE_COUNT 0x00000080 +#define chipMinorFeatures2_UNK8 0x00000100 +#define chipMinorFeatures2_UNK9 0x00000200 +#define chipMinorFeatures2_UNK10 0x00000400 +#define chipMinorFeatures2_HALTI1 0x00000800 +#define chipMinorFeatures2_UNK12 0x00001000 +#define chipMinorFeatures2_UNK13 0x00002000 +#define chipMinorFeatures2_UNK14 0x00004000 +#define chipMinorFeatures2_EXTRA_TEXTURE_STATE 0x00008000 +#define chipMinorFeatures2_FULL_DIRECTFB 0x00010000 +#define chipMinorFeatures2_2D_TILING 0x00020000 +#define chipMinorFeatures2_THREAD_WALKER_IN_PS 0x00040000 +#define chipMinorFeatures2_TILE_FILLER 0x00080000 +#define chipMinorFeatures2_UNK20 0x00100000 +#define chipMinorFeatures2_2D_MULTI_SOURCE_BLIT 0x00200000 +#define chipMinorFeatures2_UNK22 0x00400000 +#define chipMinorFeatures2_UNK23 0x00800000 +#define chipMinorFeatures2_UNK24 0x01000000 +#define chipMinorFeatures2_MIXED_STREAMS 0x02000000 +#define chipMinorFeatures2_2D_420_L2CACHE 0x04000000 +#define chipMinorFeatures2_UNK27 0x08000000 +#define chipMinorFeatures2_2D_NO_INDEX8_BRUSH 0x10000000 +#define chipMinorFeatures2_TEXTURE_TILED_READ 0x20000000 +#define chipMinorFeatures2_UNK30 0x40000000 +#define chipMinorFeatures2_UNK31 0x80000000 +#define chipMinorFeatures3_ROTATION_STALL_FIX 0x00000001 +#define chipMinorFeatures3_UNK1 0x00000002 +#define chipMinorFeatures3_2D_MULTI_SOURCE_BLT_EX 0x00000004 +#define chipMinorFeatures3_UNK3 0x00000008 +#define chipMinorFeatures3_UNK4 0x00000010 +#define chipMinorFeatures3_UNK5 0x00000020 +#define chipMinorFeatures3_UNK6 0x00000040 +#define chipMinorFeatures3_UNK7 0x00000080 +#define chipMinorFeatures3_FAST_MSAA 0x00000100 +#define chipMinorFeatures3_UNK9 0x00000200 +#define chipMinorFeatures3_BUG_FIXES10 0x00000400 +#define chipMinorFeatures3_UNK11 0x00000800 +#define chipMinorFeatures3_BUG_FIXES11 0x00001000 +#define chipMinorFeatures3_UNK13 0x00002000 +#define chipMinorFeatures3_UNK14 0x00004000 +#define chipMinorFeatures3_UNK15 0x00008000 +#define chipMinorFeatures3_UNK16 0x00010000 +#define chipMinorFeatures3_UNK17 0x00020000 +#define chipMinorFeatures3_ACE 0x00040000 +#define chipMinorFeatures3_UNK19 0x00080000 +#define chipMinorFeatures3_UNK20 0x00100000 +#define chipMinorFeatures3_UNK21 0x00200000 +#define chipMinorFeatures3_UNK22 0x00400000 +#define chipMinorFeatures3_UNK23 0x00800000 +#define chipMinorFeatures3_UNK24 0x01000000 +#define chipMinorFeatures3_UNK25 0x02000000 +#define chipMinorFeatures3_NEW_HZ 0x04000000 +#define chipMinorFeatures3_UNK27 0x08000000 +#define chipMinorFeatures3_UNK28 0x10000000 +#define chipMinorFeatures3_UNK29 0x20000000 +#define chipMinorFeatures3_UNK30 0x40000000 +#define chipMinorFeatures3_UNK31 0x80000000 +#define chipMinorFeatures4_UNK0 0x00000001 +#define chipMinorFeatures4_UNK1 0x00000002 +#define chipMinorFeatures4_UNK2 0x00000004 +#define chipMinorFeatures4_UNK3 0x00000008 +#define chipMinorFeatures4_UNK4 0x00000010 +#define chipMinorFeatures4_UNK5 0x00000020 +#define chipMinorFeatures4_UNK6 0x00000040 +#define chipMinorFeatures4_UNK7 0x00000080 +#define chipMinorFeatures4_UNK8 0x00000100 +#define chipMinorFeatures4_UNK9 0x00000200 +#define chipMinorFeatures4_UNK10 0x00000400 +#define chipMinorFeatures4_UNK11 0x00000800 +#define chipMinorFeatures4_UNK12 0x00001000 +#define chipMinorFeatures4_UNK13 0x00002000 +#define chipMinorFeatures4_UNK14 0x00004000 +#define chipMinorFeatures4_UNK15 0x00008000 +#define chipMinorFeatures4_HALTI2 0x00010000 +#define chipMinorFeatures4_UNK17 0x00020000 +#define chipMinorFeatures4_SMALL_MSAA 0x00040000 +#define chipMinorFeatures4_UNK19 0x00080000 +#define chipMinorFeatures4_UNK20 0x00100000 +#define chipMinorFeatures4_UNK21 0x00200000 +#define chipMinorFeatures4_UNK22 0x00400000 +#define chipMinorFeatures4_UNK23 0x00800000 +#define chipMinorFeatures4_UNK24 0x01000000 +#define chipMinorFeatures4_UNK25 0x02000000 +#define chipMinorFeatures4_UNK26 0x04000000 +#define chipMinorFeatures4_UNK27 0x08000000 +#define chipMinorFeatures4_UNK28 0x10000000 +#define chipMinorFeatures4_UNK29 0x20000000 +#define chipMinorFeatures4_UNK30 0x40000000 +#define chipMinorFeatures4_UNK31 0x80000000 +#define chipMinorFeatures5_UNK0 0x00000001 +#define chipMinorFeatures5_UNK1 0x00000002 +#define chipMinorFeatures5_UNK2 0x00000004 +#define chipMinorFeatures5_UNK3 0x00000008 +#define chipMinorFeatures5_UNK4 0x00000010 +#define chipMinorFeatures5_UNK5 0x00000020 +#define chipMinorFeatures5_UNK6 0x00000040 +#define chipMinorFeatures5_UNK7 0x00000080 +#define chipMinorFeatures5_UNK8 0x00000100 +#define chipMinorFeatures5_HALTI3 0x00000200 +#define chipMinorFeatures5_UNK10 0x00000400 +#define chipMinorFeatures5_UNK11 0x00000800 +#define chipMinorFeatures5_UNK12 0x00001000 +#define chipMinorFeatures5_UNK13 0x00002000 +#define chipMinorFeatures5_UNK14 0x00004000 +#define chipMinorFeatures5_UNK15 0x00008000 +#define chipMinorFeatures5_UNK16 0x00010000 +#define chipMinorFeatures5_UNK17 0x00020000 +#define chipMinorFeatures5_UNK18 0x00040000 +#define chipMinorFeatures5_UNK19 0x00080000 +#define chipMinorFeatures5_UNK20 0x00100000 +#define chipMinorFeatures5_UNK21 0x00200000 +#define chipMinorFeatures5_UNK22 0x00400000 +#define chipMinorFeatures5_UNK23 0x00800000 +#define chipMinorFeatures5_UNK24 0x01000000 +#define chipMinorFeatures5_UNK25 0x02000000 +#define chipMinorFeatures5_UNK26 0x04000000 +#define chipMinorFeatures5_UNK27 0x08000000 +#define chipMinorFeatures5_UNK28 0x10000000 +#define chipMinorFeatures5_UNK29 0x20000000 +#define chipMinorFeatures5_UNK30 0x40000000 +#define chipMinorFeatures5_UNK31 0x80000000 + +#endif /* COMMON_XML */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c new file mode 100644 index 0000000..332c55e --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_buffer.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2014 Etnaviv Project + * Author: Christian Gmeiner + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "etnaviv_gpu.h" +#include "etnaviv_gem.h" +#include "etnaviv_mmu.h" + +#include "common.xml.h" +#include "state.xml.h" +#include "cmdstream.xml.h" + +/* + * Command Buffer helper: + */ + + +static inline void OUT(struct etnaviv_cmdbuf *buffer, u32 data) +{ + u32 *vaddr = (u32 *)buffer->vaddr; + + BUG_ON(buffer->user_size >= buffer->size); + + vaddr[buffer->user_size / 4] = data; + buffer->user_size += 4; +} + +static inline void CMD_LOAD_STATE(struct etnaviv_cmdbuf *buffer, + u32 reg, u32 value) +{ + u32 index = reg >> VIV_FE_LOAD_STATE_HEADER_OFFSET__SHR; + + buffer->user_size = ALIGN(buffer->user_size, 8); + + /* write a register via cmd stream */ + OUT(buffer, VIV_FE_LOAD_STATE_HEADER_OP_LOAD_STATE | + VIV_FE_LOAD_STATE_HEADER_COUNT(1) | + VIV_FE_LOAD_STATE_HEADER_OFFSET(index)); + OUT(buffer, value); +} + +static inline void CMD_END(struct etnaviv_cmdbuf *buffer) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_END_HEADER_OP_END); +} + +static inline void CMD_WAIT(struct etnaviv_cmdbuf *buffer) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_WAIT_HEADER_OP_WAIT | 200); +} + +static inline void CMD_LINK(struct etnaviv_cmdbuf *buffer, + u16 prefetch, u32 address) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_LINK_HEADER_OP_LINK | + VIV_FE_LINK_HEADER_PREFETCH(prefetch)); + OUT(buffer, address); +} + +static inline void CMD_STALL(struct etnaviv_cmdbuf *buffer, + u32 from, u32 to) +{ + buffer->user_size = ALIGN(buffer->user_size, 8); + + OUT(buffer, VIV_FE_STALL_HEADER_OP_STALL); + OUT(buffer, VIV_FE_STALL_TOKEN_FROM(from) | VIV_FE_STALL_TOKEN_TO(to)); +} + +static void etnaviv_cmd_select_pipe(struct etnaviv_cmdbuf *buffer, u8 pipe) +{ + u32 flush; + u32 stall; + + /* + * This assumes that if we're switching to 2D, we're switching + * away from 3D, and vice versa. Hence, if we're switching to + * the 2D core, we need to flush the 3D depth and color caches, + * otherwise we need to flush the 2D pixel engine cache. + */ + if (pipe == ETNA_PIPE_2D) + flush = VIVS_GL_FLUSH_CACHE_DEPTH | VIVS_GL_FLUSH_CACHE_COLOR; + else + flush = VIVS_GL_FLUSH_CACHE_PE2D; + + stall = VIVS_GL_SEMAPHORE_TOKEN_FROM(SYNC_RECIPIENT_FE) | + VIVS_GL_SEMAPHORE_TOKEN_TO(SYNC_RECIPIENT_PE); + + CMD_LOAD_STATE(buffer, VIVS_GL_FLUSH_CACHE, flush); + CMD_LOAD_STATE(buffer, VIVS_GL_SEMAPHORE_TOKEN, stall); + + CMD_STALL(buffer, SYNC_RECIPIENT_FE, SYNC_RECIPIENT_PE); + + CMD_LOAD_STATE(buffer, VIVS_GL_PIPE_SELECT, + VIVS_GL_PIPE_SELECT_PIPE(pipe)); +} + +static u32 gpu_va(struct etnaviv_gpu *gpu, struct etnaviv_cmdbuf *buf) +{ + return buf->paddr - gpu->memory_base; +} + +static void etnaviv_buffer_dump(struct etnaviv_gpu *gpu, + struct etnaviv_cmdbuf *buf, u32 off, u32 len) +{ + u32 size = buf->size; + u32 *ptr = buf->vaddr + off; + + dev_info(gpu->dev, "virt %p phys 0x%08x free 0x%08x\n", + ptr, gpu_va(gpu, buf) + off, size - len * 4 - off); + + print_hex_dump(KERN_INFO, "cmd ", DUMP_PREFIX_OFFSET, 16, 4, + ptr, len * 4, 0); +} + +u16 etnaviv_buffer_init(struct etnaviv_gpu *gpu) +{ + struct etnaviv_cmdbuf *buffer = gpu->buffer; + + /* initialize buffer */ + buffer->user_size = 0; + + CMD_WAIT(buffer); + CMD_LINK(buffer, 2, gpu_va(gpu, buffer) + buffer->user_size - 4); + + return buffer->user_size / 8; +} + +void etnaviv_buffer_end(struct etnaviv_gpu *gpu) +{ + struct etnaviv_cmdbuf *buffer = gpu->buffer; + + /* Replace the last WAIT with an END */ + buffer->user_size -= 16; + + CMD_END(buffer); + mb(); +} + +void etnaviv_buffer_queue(struct etnaviv_gpu *gpu, unsigned int event, + struct etnaviv_cmdbuf *cmdbuf) +{ + struct etnaviv_cmdbuf *buffer = gpu->buffer; + u32 *lw = buffer->vaddr + buffer->user_size - 16; + u32 back, link_target, link_size, reserve_size, extra_size = 0; + + if (drm_debug & DRM_UT_DRIVER) + etnaviv_buffer_dump(gpu, buffer, 0, 0x50); + + /* + * If we need to flush the MMU prior to submitting this buffer, we + * will need to append a mmu flush load state, followed by a new + * link to this buffer - a total of four additional words. + */ + if (gpu->mmu->need_flush || gpu->switch_context) { + /* link command */ + extra_size += 2; + /* flush command */ + if (gpu->mmu->need_flush) + extra_size += 2; + /* pipe switch commands */ + if (gpu->switch_context) + extra_size += 8; + } + + reserve_size = (6 + extra_size) * 4; + + /* + * if we are going to completely overflow the buffer, we need to wrap. + */ + if (buffer->user_size + reserve_size > buffer->size) + buffer->user_size = 0; + + /* save offset back into main buffer */ + back = buffer->user_size + reserve_size - 6 * 4; + link_target = gpu_va(gpu, buffer) + buffer->user_size; + link_size = 6; + + /* Skip over any extra instructions */ + link_target += extra_size * sizeof(u32); + + if (drm_debug & DRM_UT_DRIVER) + pr_info("stream link to 0x%08x @ 0x%08x %p\n", + link_target, gpu_va(gpu, cmdbuf), cmdbuf->vaddr); + + /* jump back from cmd to main buffer */ + CMD_LINK(cmdbuf, link_size, link_target); + + link_target = gpu_va(gpu, cmdbuf); + link_size = cmdbuf->size / 8; + + + + if (drm_debug & DRM_UT_DRIVER) { + print_hex_dump(KERN_INFO, "cmd ", DUMP_PREFIX_OFFSET, 16, 4, + cmdbuf->vaddr, cmdbuf->size, 0); + + pr_info("link op: %p\n", lw); + pr_info("link addr: %p\n", lw + 1); + pr_info("addr: 0x%08x\n", link_target); + pr_info("back: 0x%08x\n", gpu_va(gpu, buffer) + back); + pr_info("event: %d\n", event); + } + + if (gpu->mmu->need_flush || gpu->switch_context) { + u32 new_target = gpu_va(gpu, buffer) + buffer->user_size; + + if (gpu->mmu->need_flush) { + /* Add the MMU flush */ + CMD_LOAD_STATE(buffer, VIVS_GL_FLUSH_MMU, + VIVS_GL_FLUSH_MMU_FLUSH_FEMMU | + VIVS_GL_FLUSH_MMU_FLUSH_UNK1 | + VIVS_GL_FLUSH_MMU_FLUSH_UNK2 | + VIVS_GL_FLUSH_MMU_FLUSH_PEMMU | + VIVS_GL_FLUSH_MMU_FLUSH_UNK4); + + gpu->mmu->need_flush = false; + } + + if (gpu->switch_context) { + etnaviv_cmd_select_pipe(buffer, cmdbuf->exec_state); + gpu->switch_context = false; + } + + /* And the link to the first buffer */ + CMD_LINK(buffer, link_size, link_target); + + /* Update the link target to point to above instructions */ + link_target = new_target; + link_size = extra_size; + } + + /* trigger event */ + CMD_LOAD_STATE(buffer, VIVS_GL_EVENT, VIVS_GL_EVENT_EVENT_ID(event) | + VIVS_GL_EVENT_FROM_PE); + + /* append WAIT/LINK to main buffer */ + CMD_WAIT(buffer); + CMD_LINK(buffer, 2, gpu_va(gpu, buffer) + (buffer->user_size - 4)); + + /* Change WAIT into a LINK command; write the address first. */ + *(lw + 1) = link_target; + mb(); + *(lw) = VIV_FE_LINK_HEADER_OP_LINK | + VIV_FE_LINK_HEADER_PREFETCH(link_size); + mb(); + + if (drm_debug & DRM_UT_DRIVER) + etnaviv_buffer_dump(gpu, buffer, 0, 0x50); +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_cmd_parser.c b/drivers/gpu/drm/etnaviv/etnaviv_cmd_parser.c new file mode 100644 index 0000000..dcfd565 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_cmd_parser.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include + +#include "etnaviv_gem.h" +#include "etnaviv_gpu.h" + +#include "cmdstream.xml.h" + +#define EXTRACT(val, field) (((val) & field##__MASK) >> field##__SHIFT) + +struct etna_validation_state { + struct etnaviv_gpu *gpu; + const struct drm_etnaviv_gem_submit_reloc *relocs; + unsigned int num_relocs; + u32 *start; +}; + +static const struct { + u16 offset; + u16 size; +} etnaviv_sensitive_states[] __initconst = { +#define ST(start, num) { (start) >> 2, (num) } + /* 2D */ + ST(0x1200, 1), + ST(0x1228, 1), + ST(0x1238, 1), + ST(0x1284, 1), + ST(0x128c, 1), + ST(0x1304, 1), + ST(0x1310, 1), + ST(0x1318, 1), + ST(0x12800, 4), + ST(0x128a0, 4), + ST(0x128c0, 4), + ST(0x12970, 4), + ST(0x12a00, 8), + ST(0x12b40, 8), + ST(0x12b80, 8), + ST(0x12ce0, 8), + /* 3D */ + ST(0x0644, 1), + ST(0x064c, 1), + ST(0x0680, 8), + ST(0x1410, 1), + ST(0x1430, 1), + ST(0x1458, 1), + ST(0x1460, 8), + ST(0x1480, 8), + ST(0x1500, 8), + ST(0x1520, 8), + ST(0x1608, 1), + ST(0x1610, 1), + ST(0x1658, 1), + ST(0x165c, 1), + ST(0x1664, 1), + ST(0x1668, 1), + ST(0x16a4, 1), + ST(0x16c0, 8), + ST(0x16e0, 8), + ST(0x1740, 8), + ST(0x2400, 14 * 16), + ST(0x10800, 32 * 16), +#undef ST +}; + +#define ETNAVIV_STATES_SIZE (VIV_FE_LOAD_STATE_HEADER_OFFSET__MASK + 1u) +static DECLARE_BITMAP(etnaviv_states, ETNAVIV_STATES_SIZE); + +void __init etnaviv_validate_init(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(etnaviv_sensitive_states); i++) + bitmap_set(etnaviv_states, etnaviv_sensitive_states[i].offset, + etnaviv_sensitive_states[i].size); +} + +static void etnaviv_warn_if_non_sensitive(struct etna_validation_state *state, + unsigned int buf_offset, unsigned int state_addr) +{ + if (state->num_relocs && state->relocs->submit_offset < buf_offset) { + dev_warn_once(state->gpu->dev, + "%s: relocation for non-sensitive state 0x%x at offset %u\n", + __func__, state_addr, + state->relocs->submit_offset); + while (state->num_relocs && + state->relocs->submit_offset < buf_offset) { + state->relocs++; + state->num_relocs--; + } + } +} + +static bool etnaviv_validate_load_state(struct etna_validation_state *state, + u32 *ptr, unsigned int state_offset, unsigned int num) +{ + unsigned int size = min(ETNAVIV_STATES_SIZE, state_offset + num); + unsigned int st_offset = state_offset, buf_offset; + + for_each_set_bit_from(st_offset, etnaviv_states, size) { + buf_offset = (ptr - state->start + + st_offset - state_offset) * 4; + + etnaviv_warn_if_non_sensitive(state, buf_offset, st_offset * 4); + if (state->num_relocs && + state->relocs->submit_offset == buf_offset) { + state->relocs++; + state->num_relocs--; + continue; + } + + dev_warn_ratelimited(state->gpu->dev, + "%s: load state touches restricted state 0x%x at offset %u\n", + __func__, st_offset * 4, buf_offset); + return false; + } + + if (state->num_relocs) { + buf_offset = (ptr - state->start + num) * 4; + etnaviv_warn_if_non_sensitive(state, buf_offset, st_offset * 4 + + state->relocs->submit_offset - + buf_offset); + } + + return true; +} + +static uint8_t cmd_length[32] = { + [FE_OPCODE_DRAW_PRIMITIVES] = 4, + [FE_OPCODE_DRAW_INDEXED_PRIMITIVES] = 6, + [FE_OPCODE_NOP] = 2, + [FE_OPCODE_STALL] = 2, +}; + +bool etnaviv_cmd_validate_one(struct etnaviv_gpu *gpu, u32 *stream, + unsigned int size, + struct drm_etnaviv_gem_submit_reloc *relocs, + unsigned int reloc_size) +{ + struct etna_validation_state state; + u32 *buf = stream; + u32 *end = buf + size; + + state.gpu = gpu; + state.relocs = relocs; + state.num_relocs = reloc_size; + state.start = stream; + + while (buf < end) { + u32 cmd = *buf; + unsigned int len, n, off; + unsigned int op = cmd >> 27; + + switch (op) { + case FE_OPCODE_LOAD_STATE: + n = EXTRACT(cmd, VIV_FE_LOAD_STATE_HEADER_COUNT); + len = ALIGN(1 + n, 2); + if (buf + len > end) + break; + + off = EXTRACT(cmd, VIV_FE_LOAD_STATE_HEADER_OFFSET); + if (!etnaviv_validate_load_state(&state, buf + 1, + off, n)) + return false; + break; + + case FE_OPCODE_DRAW_2D: + n = EXTRACT(cmd, VIV_FE_DRAW_2D_HEADER_COUNT); + if (n == 0) + n = 256; + len = 2 + n * 2; + break; + + default: + len = cmd_length[op]; + if (len == 0) { + dev_err(gpu->dev, "%s: op %u not permitted at offset %tu\n", + __func__, op, buf - state.start); + return false; + } + break; + } + + buf += len; + } + + if (buf > end) { + dev_err(gpu->dev, "%s: commands overflow end of buffer: %tu > %u\n", + __func__, buf - state.start, size); + return false; + } + + return true; +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_drv.c b/drivers/gpu/drm/etnaviv/etnaviv_drv.c new file mode 100644 index 0000000..e885898 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.c @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include + +#include "etnaviv_drv.h" +#include "etnaviv_gpu.h" +#include "etnaviv_gem.h" +#include "etnaviv_mmu.h" +#include "etnaviv_gem.h" + +#ifdef CONFIG_DRM_ETNAVIV_REGISTER_LOGGING +static bool reglog; +MODULE_PARM_DESC(reglog, "Enable register read/write logging"); +module_param(reglog, bool, 0600); +#else +#define reglog 0 +#endif + +void __iomem *etnaviv_ioremap(struct platform_device *pdev, const char *name, + const char *dbgname) +{ + struct resource *res; + void __iomem *ptr; + + if (name) + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + else + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + ptr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ptr)) { + dev_err(&pdev->dev, "failed to ioremap %s: %ld\n", name, + PTR_ERR(ptr)); + return ptr; + } + + if (reglog) + dev_printk(KERN_DEBUG, &pdev->dev, "IO:region %s 0x%p %08zx\n", + dbgname, ptr, (size_t)resource_size(res)); + + return ptr; +} + +void etnaviv_writel(u32 data, void __iomem *addr) +{ + if (reglog) + printk(KERN_DEBUG "IO:W %p %08x\n", addr, data); + + writel(data, addr); +} + +u32 etnaviv_readl(const void __iomem *addr) +{ + u32 val = readl(addr); + + if (reglog) + printk(KERN_DEBUG "IO:R %p %08x\n", addr, val); + + return val; +} + +/* + * DRM operations: + */ + + +static void load_gpu(struct drm_device *dev) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + unsigned int i; + + for (i = 0; i < ETNA_MAX_PIPES; i++) { + struct etnaviv_gpu *g = priv->gpu[i]; + + if (g) { + int ret; + + ret = etnaviv_gpu_init(g); + if (ret) { + dev_err(g->dev, "hw init failed: %d\n", ret); + priv->gpu[i] = NULL; + } + } + } +} + +static int etnaviv_open(struct drm_device *dev, struct drm_file *file) +{ + struct etnaviv_file_private *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + file->driver_priv = ctx; + + return 0; +} + +static void etnaviv_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + struct etnaviv_file_private *ctx = file->driver_priv; + unsigned int i; + + for (i = 0; i < ETNA_MAX_PIPES; i++) { + struct etnaviv_gpu *gpu = priv->gpu[i]; + + if (gpu) { + mutex_lock(&gpu->lock); + if (gpu->lastctx == ctx) + gpu->lastctx = NULL; + mutex_unlock(&gpu->lock); + } + } + + kfree(ctx); +} + +/* + * DRM debugfs: + */ + +#ifdef CONFIG_DEBUG_FS +static int etnaviv_gem_show(struct drm_device *dev, struct seq_file *m) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + + etnaviv_gem_describe_objects(priv, m); + + return 0; +} + +static int etnaviv_mm_show(struct drm_device *dev, struct seq_file *m) +{ + int ret; + + read_lock(&dev->vma_offset_manager->vm_lock); + ret = drm_mm_dump_table(m, &dev->vma_offset_manager->vm_addr_space_mm); + read_unlock(&dev->vma_offset_manager->vm_lock); + + return ret; +} + +static int etnaviv_mmu_show(struct etnaviv_gpu *gpu, struct seq_file *m) +{ + seq_printf(m, "Active Objects (%s):\n", dev_name(gpu->dev)); + + mutex_lock(&gpu->mmu->lock); + drm_mm_dump_table(m, &gpu->mmu->mm); + mutex_unlock(&gpu->mmu->lock); + + return 0; +} + +static void etnaviv_buffer_dump(struct etnaviv_gpu *gpu, struct seq_file *m) +{ + struct etnaviv_cmdbuf *buf = gpu->buffer; + u32 size = buf->size; + u32 *ptr = buf->vaddr; + u32 i; + + seq_printf(m, "virt %p - phys 0x%llx - free 0x%08x\n", + buf->vaddr, (u64)buf->paddr, size - buf->user_size); + + for (i = 0; i < size / 4; i++) { + if (i && !(i % 4)) + seq_puts(m, "\n"); + if (i % 4 == 0) + seq_printf(m, "\t0x%p: ", ptr + i); + seq_printf(m, "%08x ", *(ptr + i)); + } + seq_puts(m, "\n"); +} + +static int etnaviv_ring_show(struct etnaviv_gpu *gpu, struct seq_file *m) +{ + seq_printf(m, "Ring Buffer (%s): ", dev_name(gpu->dev)); + + mutex_lock(&gpu->lock); + etnaviv_buffer_dump(gpu, m); + mutex_unlock(&gpu->lock); + + return 0; +} + +static int show_unlocked(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + int (*show)(struct drm_device *dev, struct seq_file *m) = + node->info_ent->data; + + return show(dev, m); +} + +static int show_each_gpu(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct etnaviv_drm_private *priv = dev->dev_private; + struct etnaviv_gpu *gpu; + int (*show)(struct etnaviv_gpu *gpu, struct seq_file *m) = + node->info_ent->data; + unsigned int i; + int ret = 0; + + for (i = 0; i < ETNA_MAX_PIPES; i++) { + gpu = priv->gpu[i]; + if (!gpu) + continue; + + ret = show(gpu, m); + if (ret < 0) + break; + } + + return ret; +} + +static struct drm_info_list etnaviv_debugfs_list[] = { + {"gpu", show_each_gpu, 0, etnaviv_gpu_debugfs}, + {"gem", show_unlocked, 0, etnaviv_gem_show}, + { "mm", show_unlocked, 0, etnaviv_mm_show }, + {"mmu", show_each_gpu, 0, etnaviv_mmu_show}, + {"ring", show_each_gpu, 0, etnaviv_ring_show}, +}; + +static int etnaviv_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *dev = minor->dev; + int ret; + + ret = drm_debugfs_create_files(etnaviv_debugfs_list, + ARRAY_SIZE(etnaviv_debugfs_list), + minor->debugfs_root, minor); + + if (ret) { + dev_err(dev->dev, "could not install etnaviv_debugfs_list\n"); + return ret; + } + + return ret; +} + +static void etnaviv_debugfs_cleanup(struct drm_minor *minor) +{ + drm_debugfs_remove_files(etnaviv_debugfs_list, + ARRAY_SIZE(etnaviv_debugfs_list), minor); +} +#endif + +/* + * DRM ioctls: + */ + +static int etnaviv_ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + struct drm_etnaviv_param *args = data; + struct etnaviv_gpu *gpu; + + if (args->pipe >= ETNA_MAX_PIPES) + return -EINVAL; + + gpu = priv->gpu[args->pipe]; + if (!gpu) + return -ENXIO; + + return etnaviv_gpu_get_param(gpu, args->param, &args->value); +} + +static int etnaviv_ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_gem_new *args = data; + + if (args->flags & ~(ETNA_BO_CACHED | ETNA_BO_WC | ETNA_BO_UNCACHED | + ETNA_BO_FORCE_MMU)) + return -EINVAL; + + return etnaviv_gem_new_handle(dev, file, args->size, + args->flags, &args->handle); +} + +#define TS(t) ((struct timespec){ \ + .tv_sec = (t).tv_sec, \ + .tv_nsec = (t).tv_nsec \ +}) + +static int etnaviv_ioctl_gem_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_gem_cpu_prep *args = data; + struct drm_gem_object *obj; + int ret; + + if (args->op & ~(ETNA_PREP_READ | ETNA_PREP_WRITE | ETNA_PREP_NOSYNC)) + return -EINVAL; + + obj = drm_gem_object_lookup(dev, file, args->handle); + if (!obj) + return -ENOENT; + + ret = etnaviv_gem_cpu_prep(obj, args->op, &TS(args->timeout)); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int etnaviv_ioctl_gem_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_gem_cpu_fini *args = data; + struct drm_gem_object *obj; + int ret; + + if (args->flags) + return -EINVAL; + + obj = drm_gem_object_lookup(dev, file, args->handle); + if (!obj) + return -ENOENT; + + ret = etnaviv_gem_cpu_fini(obj); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int etnaviv_ioctl_gem_info(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_gem_info *args = data; + struct drm_gem_object *obj; + int ret; + + if (args->pad) + return -EINVAL; + + obj = drm_gem_object_lookup(dev, file, args->handle); + if (!obj) + return -ENOENT; + + ret = etnaviv_gem_mmap_offset(obj, &args->offset); + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int etnaviv_ioctl_wait_fence(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_wait_fence *args = data; + struct etnaviv_drm_private *priv = dev->dev_private; + struct timespec *timeout = &TS(args->timeout); + struct etnaviv_gpu *gpu; + + if (args->flags & ~(ETNA_WAIT_NONBLOCK)) + return -EINVAL; + + if (args->pipe >= ETNA_MAX_PIPES) + return -EINVAL; + + gpu = priv->gpu[args->pipe]; + if (!gpu) + return -ENXIO; + + if (args->flags & ETNA_WAIT_NONBLOCK) + timeout = NULL; + + return etnaviv_gpu_wait_fence_interruptible(gpu, args->fence, + timeout); +} + +static int etnaviv_ioctl_gem_userptr(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct drm_etnaviv_gem_userptr *args = data; + int access; + + if (args->flags & ~(ETNA_USERPTR_READ|ETNA_USERPTR_WRITE) || + args->flags == 0) + return -EINVAL; + + if (offset_in_page(args->user_ptr | args->user_size) || + (uintptr_t)args->user_ptr != args->user_ptr || + (u32)args->user_size != args->user_size || + args->user_ptr & ~PAGE_MASK) + return -EINVAL; + + if (args->flags & ETNA_USERPTR_WRITE) + access = VERIFY_WRITE; + else + access = VERIFY_READ; + + if (!access_ok(access, (void __user *)(unsigned long)args->user_ptr, + args->user_size)) + return -EFAULT; + + return etnaviv_gem_new_userptr(dev, file, args->user_ptr, + args->user_size, args->flags, + &args->handle); +} + +static int etnaviv_ioctl_gem_wait(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + struct drm_etnaviv_gem_wait *args = data; + struct timespec *timeout = &TS(args->timeout); + struct drm_gem_object *obj; + struct etnaviv_gpu *gpu; + int ret; + + if (args->flags & ~(ETNA_WAIT_NONBLOCK)) + return -EINVAL; + + if (args->pipe >= ETNA_MAX_PIPES) + return -EINVAL; + + gpu = priv->gpu[args->pipe]; + if (!gpu) + return -ENXIO; + + obj = drm_gem_object_lookup(dev, file, args->handle); + if (!obj) + return -ENOENT; + + if (args->flags & ETNA_WAIT_NONBLOCK) + timeout = NULL; + + ret = etnaviv_gem_wait_bo(gpu, obj, timeout); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static const struct drm_ioctl_desc etnaviv_ioctls[] = { +#define ETNA_IOCTL(n, func, flags) \ + DRM_IOCTL_DEF_DRV(ETNAVIV_##n, etnaviv_ioctl_##func, flags) + ETNA_IOCTL(GET_PARAM, get_param, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_NEW, gem_new, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_INFO, gem_info, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_CPU_PREP, gem_cpu_prep, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_CPU_FINI, gem_cpu_fini, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_SUBMIT, gem_submit, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(WAIT_FENCE, wait_fence, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_USERPTR, gem_userptr, DRM_AUTH|DRM_RENDER_ALLOW), + ETNA_IOCTL(GEM_WAIT, gem_wait, DRM_AUTH|DRM_RENDER_ALLOW), +}; + +static const struct vm_operations_struct vm_ops = { + .fault = etnaviv_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = etnaviv_gem_mmap, +}; + +static struct drm_driver etnaviv_drm_driver = { + .driver_features = DRIVER_HAVE_IRQ | + DRIVER_GEM | + DRIVER_PRIME | + DRIVER_RENDER, + .open = etnaviv_open, + .preclose = etnaviv_preclose, + .set_busid = drm_platform_set_busid, + .gem_free_object = etnaviv_gem_free_object, + .gem_vm_ops = &vm_ops, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_pin = etnaviv_gem_prime_pin, + .gem_prime_unpin = etnaviv_gem_prime_unpin, + .gem_prime_get_sg_table = etnaviv_gem_prime_get_sg_table, + .gem_prime_import_sg_table = etnaviv_gem_prime_import_sg_table, + .gem_prime_vmap = etnaviv_gem_prime_vmap, + .gem_prime_vunmap = etnaviv_gem_prime_vunmap, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = etnaviv_debugfs_init, + .debugfs_cleanup = etnaviv_debugfs_cleanup, +#endif + .ioctls = etnaviv_ioctls, + .num_ioctls = DRM_ETNAVIV_NUM_IOCTLS, + .fops = &fops, + .name = "etnaviv", + .desc = "etnaviv DRM", + .date = "20151214", + .major = 1, + .minor = 0, +}; + +/* + * Platform driver: + */ +static int etnaviv_bind(struct device *dev) +{ + struct etnaviv_drm_private *priv; + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&etnaviv_drm_driver, dev); + if (!drm) + return -ENOMEM; + + drm->platformdev = to_platform_device(dev); + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev, "failed to allocate private data\n"); + ret = -ENOMEM; + goto out_unref; + } + drm->dev_private = priv; + + priv->wq = alloc_ordered_workqueue("etnaviv", 0); + if (!priv->wq) { + ret = -ENOMEM; + goto out_wq; + } + + mutex_init(&priv->gem_lock); + INIT_LIST_HEAD(&priv->gem_list); + priv->num_gpus = 0; + + dev_set_drvdata(dev, drm); + + ret = component_bind_all(dev, drm); + if (ret < 0) + goto out_bind; + + load_gpu(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto out_register; + + return 0; + +out_register: + component_unbind_all(dev, drm); +out_bind: + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); +out_wq: + kfree(priv); +out_unref: + drm_dev_unref(drm); + + return ret; +} + +static void etnaviv_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct etnaviv_drm_private *priv = drm->dev_private; + + drm_dev_unregister(drm); + + flush_workqueue(priv->wq); + destroy_workqueue(priv->wq); + + component_unbind_all(dev, drm); + + drm->dev_private = NULL; + kfree(priv); + + drm_put_dev(drm); +} + +static const struct component_master_ops etnaviv_master_ops = { + .bind = etnaviv_bind, + .unbind = etnaviv_unbind, +}; + +static int compare_of(struct device *dev, void *data) +{ + struct device_node *np = data; + + return dev->of_node == np; +} + +static int compare_str(struct device *dev, void *data) +{ + return !strcmp(dev_name(dev), data); +} + +static int etnaviv_pdev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct component_match *match = NULL; + + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + if (node) { + struct device_node *core_node; + int i; + + for (i = 0; ; i++) { + core_node = of_parse_phandle(node, "cores", i); + if (!core_node) + break; + + component_match_add(&pdev->dev, &match, compare_of, + core_node); + of_node_put(core_node); + } + } else if (dev->platform_data) { + char **names = dev->platform_data; + unsigned i; + + for (i = 0; names[i]; i++) + component_match_add(dev, &match, compare_str, names[i]); + } + + return component_master_add_with_match(dev, &etnaviv_master_ops, match); +} + +static int etnaviv_pdev_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &etnaviv_master_ops); + + return 0; +} + +static const struct of_device_id dt_match[] = { + { .compatible = "fsl,imx-gpu-subsystem" }, + { .compatible = "marvell,dove-gpu-subsystem" }, + {} +}; +MODULE_DEVICE_TABLE(of, dt_match); + +static struct platform_driver etnaviv_platform_driver = { + .probe = etnaviv_pdev_probe, + .remove = etnaviv_pdev_remove, + .driver = { + .name = "etnaviv", + .of_match_table = dt_match, + }, +}; + +static int __init etnaviv_init(void) +{ + int ret; + + etnaviv_validate_init(); + + ret = platform_driver_register(&etnaviv_gpu_driver); + if (ret != 0) + return ret; + + ret = platform_driver_register(&etnaviv_platform_driver); + if (ret != 0) + platform_driver_unregister(&etnaviv_gpu_driver); + + return ret; +} +module_init(etnaviv_init); + +static void __exit etnaviv_exit(void) +{ + platform_driver_unregister(&etnaviv_gpu_driver); + platform_driver_unregister(&etnaviv_platform_driver); +} +module_exit(etnaviv_exit); + +MODULE_AUTHOR("Christian Gmeiner "); +MODULE_AUTHOR("Russell King "); +MODULE_AUTHOR("Lucas Stach "); +MODULE_DESCRIPTION("etnaviv DRM Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:etnaviv"); diff --git b/drivers/gpu/drm/etnaviv/etnaviv_drv.h b/drivers/gpu/drm/etnaviv/etnaviv_drv.h new file mode 100644 index 0000000..1cd6046 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_drv.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_DRV_H__ +#define __ETNAVIV_DRV_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct etnaviv_cmdbuf; +struct etnaviv_gpu; +struct etnaviv_mmu; +struct etnaviv_gem_object; +struct etnaviv_gem_submit; + +struct etnaviv_file_private { + /* currently we don't do anything useful with this.. but when + * per-context address spaces are supported we'd keep track of + * the context's page-tables here. + */ + int dummy; +}; + +struct etnaviv_drm_private { + int num_gpus; + struct etnaviv_gpu *gpu[ETNA_MAX_PIPES]; + + /* list of GEM objects: */ + struct mutex gem_lock; + struct list_head gem_list; + + struct workqueue_struct *wq; +}; + +static inline void etnaviv_queue_work(struct drm_device *dev, + struct work_struct *w) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + + queue_work(priv->wq, w); +} + +int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data, + struct drm_file *file); + +int etnaviv_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int etnaviv_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int etnaviv_gem_mmap_offset(struct drm_gem_object *obj, u64 *offset); +int etnaviv_gem_get_iova(struct etnaviv_gpu *gpu, + struct drm_gem_object *obj, u32 *iova); +void etnaviv_gem_put_iova(struct etnaviv_gpu *gpu, struct drm_gem_object *obj); +struct sg_table *etnaviv_gem_prime_get_sg_table(struct drm_gem_object *obj); +void *etnaviv_gem_prime_vmap(struct drm_gem_object *obj); +void etnaviv_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr); +struct drm_gem_object *etnaviv_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sg); +int etnaviv_gem_prime_pin(struct drm_gem_object *obj); +void etnaviv_gem_prime_unpin(struct drm_gem_object *obj); +void *etnaviv_gem_vmap(struct drm_gem_object *obj); +int etnaviv_gem_cpu_prep(struct drm_gem_object *obj, u32 op, + struct timespec *timeout); +int etnaviv_gem_cpu_fini(struct drm_gem_object *obj); +void etnaviv_gem_free_object(struct drm_gem_object *obj); +int etnaviv_gem_new_handle(struct drm_device *dev, struct drm_file *file, + u32 size, u32 flags, u32 *handle); +struct drm_gem_object *etnaviv_gem_new_locked(struct drm_device *dev, + u32 size, u32 flags); +struct drm_gem_object *etnaviv_gem_new(struct drm_device *dev, + u32 size, u32 flags); +int etnaviv_gem_new_userptr(struct drm_device *dev, struct drm_file *file, + uintptr_t ptr, u32 size, u32 flags, u32 *handle); +u16 etnaviv_buffer_init(struct etnaviv_gpu *gpu); +void etnaviv_buffer_end(struct etnaviv_gpu *gpu); +void etnaviv_buffer_queue(struct etnaviv_gpu *gpu, unsigned int event, + struct etnaviv_cmdbuf *cmdbuf); +void etnaviv_validate_init(void); +bool etnaviv_cmd_validate_one(struct etnaviv_gpu *gpu, + u32 *stream, unsigned int size, + struct drm_etnaviv_gem_submit_reloc *relocs, unsigned int reloc_size); + +#ifdef CONFIG_DEBUG_FS +void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv, + struct seq_file *m); +#endif + +void __iomem *etnaviv_ioremap(struct platform_device *pdev, const char *name, + const char *dbgname); +void etnaviv_writel(u32 data, void __iomem *addr); +u32 etnaviv_readl(const void __iomem *addr); + +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#define VERB(fmt, ...) if (0) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) + +/* + * Return the storage size of a structure with a variable length array. + * The array is nelem elements of elem_size, where the base structure + * is defined by base. If the size overflows size_t, return zero. + */ +static inline size_t size_vstruct(size_t nelem, size_t elem_size, size_t base) +{ + if (elem_size && nelem > (SIZE_MAX - base) / elem_size) + return 0; + return base + nelem * elem_size; +} + +/* returns true if fence a comes after fence b */ +static inline bool fence_after(u32 a, u32 b) +{ + return (s32)(a - b) > 0; +} + +static inline bool fence_after_eq(u32 a, u32 b) +{ + return (s32)(a - b) >= 0; +} + +static inline unsigned long etnaviv_timeout_to_jiffies( + const struct timespec *timeout) +{ + unsigned long timeout_jiffies = timespec_to_jiffies(timeout); + unsigned long start_jiffies = jiffies; + unsigned long remaining_jiffies; + + if (time_after(start_jiffies, timeout_jiffies)) + remaining_jiffies = 0; + else + remaining_jiffies = timeout_jiffies - start_jiffies; + + return remaining_jiffies; +} + +#endif /* __ETNAVIV_DRV_H__ */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_dump.c b/drivers/gpu/drm/etnaviv/etnaviv_dump.c new file mode 100644 index 0000000..4a29eea --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_dump.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include "etnaviv_dump.h" +#include "etnaviv_gem.h" +#include "etnaviv_gpu.h" +#include "etnaviv_mmu.h" +#include "state.xml.h" +#include "state_hi.xml.h" + +struct core_dump_iterator { + void *start; + struct etnaviv_dump_object_header *hdr; + void *data; +}; + +static const unsigned short etnaviv_dump_registers[] = { + VIVS_HI_AXI_STATUS, + VIVS_HI_CLOCK_CONTROL, + VIVS_HI_IDLE_STATE, + VIVS_HI_AXI_CONFIG, + VIVS_HI_INTR_ENBL, + VIVS_HI_CHIP_IDENTITY, + VIVS_HI_CHIP_FEATURE, + VIVS_HI_CHIP_MODEL, + VIVS_HI_CHIP_REV, + VIVS_HI_CHIP_DATE, + VIVS_HI_CHIP_TIME, + VIVS_HI_CHIP_MINOR_FEATURE_0, + VIVS_HI_CACHE_CONTROL, + VIVS_HI_AXI_CONTROL, + VIVS_PM_POWER_CONTROLS, + VIVS_PM_MODULE_CONTROLS, + VIVS_PM_MODULE_STATUS, + VIVS_PM_PULSE_EATER, + VIVS_MC_MMU_FE_PAGE_TABLE, + VIVS_MC_MMU_TX_PAGE_TABLE, + VIVS_MC_MMU_PE_PAGE_TABLE, + VIVS_MC_MMU_PEZ_PAGE_TABLE, + VIVS_MC_MMU_RA_PAGE_TABLE, + VIVS_MC_DEBUG_MEMORY, + VIVS_MC_MEMORY_BASE_ADDR_RA, + VIVS_MC_MEMORY_BASE_ADDR_FE, + VIVS_MC_MEMORY_BASE_ADDR_TX, + VIVS_MC_MEMORY_BASE_ADDR_PEZ, + VIVS_MC_MEMORY_BASE_ADDR_PE, + VIVS_MC_MEMORY_TIMING_CONTROL, + VIVS_MC_BUS_CONFIG, + VIVS_FE_DMA_STATUS, + VIVS_FE_DMA_DEBUG_STATE, + VIVS_FE_DMA_ADDRESS, + VIVS_FE_DMA_LOW, + VIVS_FE_DMA_HIGH, + VIVS_FE_AUTO_FLUSH, +}; + +static void etnaviv_core_dump_header(struct core_dump_iterator *iter, + u32 type, void *data_end) +{ + struct etnaviv_dump_object_header *hdr = iter->hdr; + + hdr->magic = cpu_to_le32(ETDUMP_MAGIC); + hdr->type = cpu_to_le32(type); + hdr->file_offset = cpu_to_le32(iter->data - iter->start); + hdr->file_size = cpu_to_le32(data_end - iter->data); + + iter->hdr++; + iter->data += hdr->file_size; +} + +static void etnaviv_core_dump_registers(struct core_dump_iterator *iter, + struct etnaviv_gpu *gpu) +{ + struct etnaviv_dump_registers *reg = iter->data; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(etnaviv_dump_registers); i++, reg++) { + reg->reg = etnaviv_dump_registers[i]; + reg->value = gpu_read(gpu, etnaviv_dump_registers[i]); + } + + etnaviv_core_dump_header(iter, ETDUMP_BUF_REG, reg); +} + +static void etnaviv_core_dump_mmu(struct core_dump_iterator *iter, + struct etnaviv_gpu *gpu, size_t mmu_size) +{ + etnaviv_iommu_dump(gpu->mmu, iter->data); + + etnaviv_core_dump_header(iter, ETDUMP_BUF_MMU, iter->data + mmu_size); +} + +static void etnaviv_core_dump_mem(struct core_dump_iterator *iter, u32 type, + void *ptr, size_t size, u64 iova) +{ + memcpy(iter->data, ptr, size); + + iter->hdr->iova = cpu_to_le64(iova); + + etnaviv_core_dump_header(iter, type, iter->data + size); +} + +void etnaviv_core_dump(struct etnaviv_gpu *gpu) +{ + struct core_dump_iterator iter; + struct etnaviv_vram_mapping *vram; + struct etnaviv_gem_object *obj; + struct etnaviv_cmdbuf *cmd; + unsigned int n_obj, n_bomap_pages; + size_t file_size, mmu_size; + __le64 *bomap, *bomap_start; + + mmu_size = etnaviv_iommu_dump_size(gpu->mmu); + + /* We always dump registers, mmu, ring and end marker */ + n_obj = 4; + n_bomap_pages = 0; + file_size = ARRAY_SIZE(etnaviv_dump_registers) * + sizeof(struct etnaviv_dump_registers) + + mmu_size + gpu->buffer->size; + + /* Add in the active command buffers */ + list_for_each_entry(cmd, &gpu->active_cmd_list, node) { + file_size += cmd->size; + n_obj++; + } + + /* Add in the active buffer objects */ + list_for_each_entry(vram, &gpu->mmu->mappings, mmu_node) { + if (!vram->use) + continue; + + obj = vram->object; + file_size += obj->base.size; + n_bomap_pages += obj->base.size >> PAGE_SHIFT; + n_obj++; + } + + /* If we have any buffer objects, add a bomap object */ + if (n_bomap_pages) { + file_size += n_bomap_pages * sizeof(__le64); + n_obj++; + } + + /* Add the size of the headers */ + file_size += sizeof(*iter.hdr) * n_obj; + + /* Allocate the file in vmalloc memory, it's likely to be big */ + iter.start = vmalloc(file_size); + if (!iter.start) { + dev_warn(gpu->dev, "failed to allocate devcoredump file\n"); + return; + } + + /* Point the data member after the headers */ + iter.hdr = iter.start; + iter.data = &iter.hdr[n_obj]; + + memset(iter.hdr, 0, iter.data - iter.start); + + etnaviv_core_dump_registers(&iter, gpu); + etnaviv_core_dump_mmu(&iter, gpu, mmu_size); + etnaviv_core_dump_mem(&iter, ETDUMP_BUF_RING, gpu->buffer->vaddr, + gpu->buffer->size, gpu->buffer->paddr); + + list_for_each_entry(cmd, &gpu->active_cmd_list, node) + etnaviv_core_dump_mem(&iter, ETDUMP_BUF_CMD, cmd->vaddr, + cmd->size, cmd->paddr); + + /* Reserve space for the bomap */ + if (n_bomap_pages) { + bomap_start = bomap = iter.data; + memset(bomap, 0, sizeof(*bomap) * n_bomap_pages); + etnaviv_core_dump_header(&iter, ETDUMP_BUF_BOMAP, + bomap + n_bomap_pages); + } else { + /* Silence warning */ + bomap_start = bomap = NULL; + } + + list_for_each_entry(vram, &gpu->mmu->mappings, mmu_node) { + struct page **pages; + void *vaddr; + + if (vram->use == 0) + continue; + + obj = vram->object; + + mutex_lock(&obj->lock); + pages = etnaviv_gem_get_pages(obj); + mutex_unlock(&obj->lock); + if (pages) { + int j; + + iter.hdr->data[0] = bomap - bomap_start; + + for (j = 0; j < obj->base.size >> PAGE_SHIFT; j++) + *bomap++ = cpu_to_le64(page_to_phys(*pages++)); + } + + iter.hdr->iova = cpu_to_le64(vram->iova); + + vaddr = etnaviv_gem_vmap(&obj->base); + if (vaddr) + memcpy(iter.data, vaddr, obj->base.size); + + etnaviv_core_dump_header(&iter, ETDUMP_BUF_BO, iter.data + + obj->base.size); + } + + etnaviv_core_dump_header(&iter, ETDUMP_BUF_END, iter.data); + + dev_coredumpv(gpu->dev, iter.start, iter.data - iter.start, GFP_KERNEL); +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_dump.h b/drivers/gpu/drm/etnaviv/etnaviv_dump.h new file mode 100644 index 0000000..97f2f8d --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_dump.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * Etnaviv devcoredump file definitions + */ +#ifndef ETNAVIV_DUMP_H +#define ETNAVIV_DUMP_H + +#include + +enum { + ETDUMP_MAGIC = 0x414e5445, + ETDUMP_BUF_REG = 0, + ETDUMP_BUF_MMU, + ETDUMP_BUF_RING, + ETDUMP_BUF_CMD, + ETDUMP_BUF_BOMAP, + ETDUMP_BUF_BO, + ETDUMP_BUF_END, +}; + +struct etnaviv_dump_object_header { + __le32 magic; + __le32 type; + __le32 file_offset; + __le32 file_size; + __le64 iova; + __le32 data[2]; +}; + +/* Registers object, an array of these */ +struct etnaviv_dump_registers { + __le32 reg; + __le32 value; +}; + +#ifdef __KERNEL__ +struct etnaviv_gpu; +void etnaviv_core_dump(struct etnaviv_gpu *gpu); +#endif + +#endif diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gem.c b/drivers/gpu/drm/etnaviv/etnaviv_gem.c new file mode 100644 index 0000000..4b519e4 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem.c @@ -0,0 +1,915 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include + +#include "etnaviv_drv.h" +#include "etnaviv_gem.h" +#include "etnaviv_gpu.h" +#include "etnaviv_mmu.h" + +static void etnaviv_gem_scatter_map(struct etnaviv_gem_object *etnaviv_obj) +{ + struct drm_device *dev = etnaviv_obj->base.dev; + struct sg_table *sgt = etnaviv_obj->sgt; + + /* + * For non-cached buffers, ensure the new pages are clean + * because display controller, GPU, etc. are not coherent. + */ + if (etnaviv_obj->flags & ETNA_BO_CACHE_MASK) + dma_map_sg(dev->dev, sgt->sgl, sgt->nents, DMA_BIDIRECTIONAL); +} + +static void etnaviv_gem_scatterlist_unmap(struct etnaviv_gem_object *etnaviv_obj) +{ + struct drm_device *dev = etnaviv_obj->base.dev; + struct sg_table *sgt = etnaviv_obj->sgt; + + /* + * For non-cached buffers, ensure the new pages are clean + * because display controller, GPU, etc. are not coherent: + * + * WARNING: The DMA API does not support concurrent CPU + * and device access to the memory area. With BIDIRECTIONAL, + * we will clean the cache lines which overlap the region, + * and invalidate all cache lines (partially) contained in + * the region. + * + * If you have dirty data in the overlapping cache lines, + * that will corrupt the GPU-written data. If you have + * written into the remainder of the region, this can + * discard those writes. + */ + if (etnaviv_obj->flags & ETNA_BO_CACHE_MASK) + dma_unmap_sg(dev->dev, sgt->sgl, sgt->nents, DMA_BIDIRECTIONAL); +} + +/* called with etnaviv_obj->lock held */ +static int etnaviv_gem_shmem_get_pages(struct etnaviv_gem_object *etnaviv_obj) +{ + struct drm_device *dev = etnaviv_obj->base.dev; + struct page **p = drm_gem_get_pages(&etnaviv_obj->base); + + if (IS_ERR(p)) { + dev_err(dev->dev, "could not get pages: %ld\n", PTR_ERR(p)); + return PTR_ERR(p); + } + + etnaviv_obj->pages = p; + + return 0; +} + +static void put_pages(struct etnaviv_gem_object *etnaviv_obj) +{ + if (etnaviv_obj->sgt) { + etnaviv_gem_scatterlist_unmap(etnaviv_obj); + sg_free_table(etnaviv_obj->sgt); + kfree(etnaviv_obj->sgt); + etnaviv_obj->sgt = NULL; + } + if (etnaviv_obj->pages) { + drm_gem_put_pages(&etnaviv_obj->base, etnaviv_obj->pages, + true, false); + + etnaviv_obj->pages = NULL; + } +} + +struct page **etnaviv_gem_get_pages(struct etnaviv_gem_object *etnaviv_obj) +{ + int ret; + + lockdep_assert_held(&etnaviv_obj->lock); + + if (!etnaviv_obj->pages) { + ret = etnaviv_obj->ops->get_pages(etnaviv_obj); + if (ret < 0) + return ERR_PTR(ret); + } + + if (!etnaviv_obj->sgt) { + struct drm_device *dev = etnaviv_obj->base.dev; + int npages = etnaviv_obj->base.size >> PAGE_SHIFT; + struct sg_table *sgt; + + sgt = drm_prime_pages_to_sg(etnaviv_obj->pages, npages); + if (IS_ERR(sgt)) { + dev_err(dev->dev, "failed to allocate sgt: %ld\n", + PTR_ERR(sgt)); + return ERR_CAST(sgt); + } + + etnaviv_obj->sgt = sgt; + + etnaviv_gem_scatter_map(etnaviv_obj); + } + + return etnaviv_obj->pages; +} + +void etnaviv_gem_put_pages(struct etnaviv_gem_object *etnaviv_obj) +{ + lockdep_assert_held(&etnaviv_obj->lock); + /* when we start tracking the pin count, then do something here */ +} + +static int etnaviv_gem_mmap_obj(struct drm_gem_object *obj, + struct vm_area_struct *vma) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + pgprot_t vm_page_prot; + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + vm_page_prot = vm_get_page_prot(vma->vm_flags); + + if (etnaviv_obj->flags & ETNA_BO_WC) { + vma->vm_page_prot = pgprot_writecombine(vm_page_prot); + } else if (etnaviv_obj->flags & ETNA_BO_UNCACHED) { + vma->vm_page_prot = pgprot_noncached(vm_page_prot); + } else { + /* + * Shunt off cached objs to shmem file so they have their own + * address_space (so unmap_mapping_range does what we want, + * in particular in the case of mmap'd dmabufs) + */ + fput(vma->vm_file); + get_file(obj->filp); + vma->vm_pgoff = 0; + vma->vm_file = obj->filp; + + vma->vm_page_prot = vm_page_prot; + } + + return 0; +} + +int etnaviv_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct etnaviv_gem_object *obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) { + DBG("mmap failed: %d", ret); + return ret; + } + + obj = to_etnaviv_bo(vma->vm_private_data); + return etnaviv_gem_mmap_obj(vma->vm_private_data, vma); +} + +int etnaviv_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct page **pages, *page; + pgoff_t pgoff; + int ret; + + /* + * Make sure we don't parallel update on a fault, nor move or remove + * something from beneath our feet. Note that vm_insert_page() is + * specifically coded to take care of this, so we don't have to. + */ + ret = mutex_lock_interruptible(&etnaviv_obj->lock); + if (ret) + goto out; + + /* make sure we have pages attached now */ + pages = etnaviv_gem_get_pages(etnaviv_obj); + mutex_unlock(&etnaviv_obj->lock); + + if (IS_ERR(pages)) { + ret = PTR_ERR(pages); + goto out; + } + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + page = pages[pgoff]; + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + page_to_pfn(page), page_to_pfn(page) << PAGE_SHIFT); + + ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address, page); + +out: + switch (ret) { + case -EAGAIN: + case 0: + case -ERESTARTSYS: + case -EINTR: + case -EBUSY: + /* + * EBUSY is ok: this just means that another thread + * already did the job. + */ + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +int etnaviv_gem_mmap_offset(struct drm_gem_object *obj, u64 *offset) +{ + int ret; + + /* Make it mmapable */ + ret = drm_gem_create_mmap_offset(obj); + if (ret) + dev_err(obj->dev->dev, "could not allocate mmap offset\n"); + else + *offset = drm_vma_node_offset_addr(&obj->vma_node); + + return ret; +} + +static struct etnaviv_vram_mapping * +etnaviv_gem_get_vram_mapping(struct etnaviv_gem_object *obj, + struct etnaviv_iommu *mmu) +{ + struct etnaviv_vram_mapping *mapping; + + list_for_each_entry(mapping, &obj->vram_list, obj_node) { + if (mapping->mmu == mmu) + return mapping; + } + + return NULL; +} + +int etnaviv_gem_get_iova(struct etnaviv_gpu *gpu, + struct drm_gem_object *obj, u32 *iova) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct etnaviv_vram_mapping *mapping; + struct page **pages; + int ret = 0; + + mutex_lock(&etnaviv_obj->lock); + mapping = etnaviv_gem_get_vram_mapping(etnaviv_obj, gpu->mmu); + if (mapping) { + /* + * Holding the object lock prevents the use count changing + * beneath us. If the use count is zero, the MMU might be + * reaping this object, so take the lock and re-check that + * the MMU owns this mapping to close this race. + */ + if (mapping->use == 0) { + mutex_lock(&gpu->mmu->lock); + if (mapping->mmu == gpu->mmu) + mapping->use += 1; + else + mapping = NULL; + mutex_unlock(&gpu->mmu->lock); + if (mapping) + goto out; + } else { + mapping->use += 1; + goto out; + } + } + + pages = etnaviv_gem_get_pages(etnaviv_obj); + if (IS_ERR(pages)) { + ret = PTR_ERR(pages); + goto out; + } + + /* + * See if we have a reaped vram mapping we can re-use before + * allocating a fresh mapping. + */ + mapping = etnaviv_gem_get_vram_mapping(etnaviv_obj, NULL); + if (!mapping) { + mapping = kzalloc(sizeof(*mapping), GFP_KERNEL); + if (!mapping) { + ret = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&mapping->scan_node); + mapping->object = etnaviv_obj; + } else { + list_del(&mapping->obj_node); + } + + mapping->mmu = gpu->mmu; + mapping->use = 1; + + ret = etnaviv_iommu_map_gem(gpu->mmu, etnaviv_obj, gpu->memory_base, + mapping); + if (ret < 0) + kfree(mapping); + else + list_add_tail(&mapping->obj_node, &etnaviv_obj->vram_list); + +out: + mutex_unlock(&etnaviv_obj->lock); + + if (!ret) { + /* Take a reference on the object */ + drm_gem_object_reference(obj); + *iova = mapping->iova; + } + + return ret; +} + +void etnaviv_gem_put_iova(struct etnaviv_gpu *gpu, struct drm_gem_object *obj) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct etnaviv_vram_mapping *mapping; + + mutex_lock(&etnaviv_obj->lock); + mapping = etnaviv_gem_get_vram_mapping(etnaviv_obj, gpu->mmu); + + WARN_ON(mapping->use == 0); + mapping->use -= 1; + mutex_unlock(&etnaviv_obj->lock); + + drm_gem_object_unreference_unlocked(obj); +} + +void *etnaviv_gem_vmap(struct drm_gem_object *obj) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + if (etnaviv_obj->vaddr) + return etnaviv_obj->vaddr; + + mutex_lock(&etnaviv_obj->lock); + /* + * Need to check again, as we might have raced with another thread + * while waiting for the mutex. + */ + if (!etnaviv_obj->vaddr) + etnaviv_obj->vaddr = etnaviv_obj->ops->vmap(etnaviv_obj); + mutex_unlock(&etnaviv_obj->lock); + + return etnaviv_obj->vaddr; +} + +static void *etnaviv_gem_vmap_impl(struct etnaviv_gem_object *obj) +{ + struct page **pages; + + lockdep_assert_held(&obj->lock); + + pages = etnaviv_gem_get_pages(obj); + if (IS_ERR(pages)) + return NULL; + + return vmap(pages, obj->base.size >> PAGE_SHIFT, + VM_MAP, pgprot_writecombine(PAGE_KERNEL)); +} + +static inline enum dma_data_direction etnaviv_op_to_dma_dir(u32 op) +{ + if (op & ETNA_PREP_READ) + return DMA_FROM_DEVICE; + else if (op & ETNA_PREP_WRITE) + return DMA_TO_DEVICE; + else + return DMA_BIDIRECTIONAL; +} + +int etnaviv_gem_cpu_prep(struct drm_gem_object *obj, u32 op, + struct timespec *timeout) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct drm_device *dev = obj->dev; + bool write = !!(op & ETNA_PREP_WRITE); + int ret; + + if (op & ETNA_PREP_NOSYNC) { + if (!reservation_object_test_signaled_rcu(etnaviv_obj->resv, + write)) + return -EBUSY; + } else { + unsigned long remain = etnaviv_timeout_to_jiffies(timeout); + + ret = reservation_object_wait_timeout_rcu(etnaviv_obj->resv, + write, true, remain); + if (ret <= 0) + return ret == 0 ? -ETIMEDOUT : ret; + } + + if (etnaviv_obj->flags & ETNA_BO_CACHED) { + if (!etnaviv_obj->sgt) { + void *ret; + + mutex_lock(&etnaviv_obj->lock); + ret = etnaviv_gem_get_pages(etnaviv_obj); + mutex_unlock(&etnaviv_obj->lock); + if (IS_ERR(ret)) + return PTR_ERR(ret); + } + + dma_sync_sg_for_cpu(dev->dev, etnaviv_obj->sgt->sgl, + etnaviv_obj->sgt->nents, + etnaviv_op_to_dma_dir(op)); + etnaviv_obj->last_cpu_prep_op = op; + } + + return 0; +} + +int etnaviv_gem_cpu_fini(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + if (etnaviv_obj->flags & ETNA_BO_CACHED) { + /* fini without a prep is almost certainly a userspace error */ + WARN_ON(etnaviv_obj->last_cpu_prep_op == 0); + dma_sync_sg_for_device(dev->dev, etnaviv_obj->sgt->sgl, + etnaviv_obj->sgt->nents, + etnaviv_op_to_dma_dir(etnaviv_obj->last_cpu_prep_op)); + etnaviv_obj->last_cpu_prep_op = 0; + } + + return 0; +} + +int etnaviv_gem_wait_bo(struct etnaviv_gpu *gpu, struct drm_gem_object *obj, + struct timespec *timeout) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + return etnaviv_gpu_wait_obj_inactive(gpu, etnaviv_obj, timeout); +} + +#ifdef CONFIG_DEBUG_FS +static void etnaviv_gem_describe_fence(struct fence *fence, + const char *type, struct seq_file *m) +{ + if (!test_bit(FENCE_FLAG_SIGNALED_BIT, &fence->flags)) + seq_printf(m, "\t%9s: %s %s seq %u\n", + type, + fence->ops->get_driver_name(fence), + fence->ops->get_timeline_name(fence), + fence->seqno); +} + +static void etnaviv_gem_describe(struct drm_gem_object *obj, struct seq_file *m) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct reservation_object *robj = etnaviv_obj->resv; + struct reservation_object_list *fobj; + struct fence *fence; + unsigned long off = drm_vma_node_start(&obj->vma_node); + + seq_printf(m, "%08x: %c %2d (%2d) %08lx %p %zd\n", + etnaviv_obj->flags, is_active(etnaviv_obj) ? 'A' : 'I', + obj->name, obj->refcount.refcount.counter, + off, etnaviv_obj->vaddr, obj->size); + + rcu_read_lock(); + fobj = rcu_dereference(robj->fence); + if (fobj) { + unsigned int i, shared_count = fobj->shared_count; + + for (i = 0; i < shared_count; i++) { + fence = rcu_dereference(fobj->shared[i]); + etnaviv_gem_describe_fence(fence, "Shared", m); + } + } + + fence = rcu_dereference(robj->fence_excl); + if (fence) + etnaviv_gem_describe_fence(fence, "Exclusive", m); + rcu_read_unlock(); +} + +void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv, + struct seq_file *m) +{ + struct etnaviv_gem_object *etnaviv_obj; + int count = 0; + size_t size = 0; + + mutex_lock(&priv->gem_lock); + list_for_each_entry(etnaviv_obj, &priv->gem_list, gem_node) { + struct drm_gem_object *obj = &etnaviv_obj->base; + + seq_puts(m, " "); + etnaviv_gem_describe(obj, m); + count++; + size += obj->size; + } + mutex_unlock(&priv->gem_lock); + + seq_printf(m, "Total %d objects, %zu bytes\n", count, size); +} +#endif + +static void etnaviv_gem_shmem_release(struct etnaviv_gem_object *etnaviv_obj) +{ + if (etnaviv_obj->vaddr) + vunmap(etnaviv_obj->vaddr); + put_pages(etnaviv_obj); +} + +static const struct etnaviv_gem_ops etnaviv_gem_shmem_ops = { + .get_pages = etnaviv_gem_shmem_get_pages, + .release = etnaviv_gem_shmem_release, + .vmap = etnaviv_gem_vmap_impl, +}; + +void etnaviv_gem_free_object(struct drm_gem_object *obj) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + struct etnaviv_vram_mapping *mapping, *tmp; + + /* object should not be active */ + WARN_ON(is_active(etnaviv_obj)); + + list_del(&etnaviv_obj->gem_node); + + list_for_each_entry_safe(mapping, tmp, &etnaviv_obj->vram_list, + obj_node) { + struct etnaviv_iommu *mmu = mapping->mmu; + + WARN_ON(mapping->use); + + if (mmu) + etnaviv_iommu_unmap_gem(mmu, mapping); + + list_del(&mapping->obj_node); + kfree(mapping); + } + + drm_gem_free_mmap_offset(obj); + etnaviv_obj->ops->release(etnaviv_obj); + if (etnaviv_obj->resv == &etnaviv_obj->_resv) + reservation_object_fini(&etnaviv_obj->_resv); + drm_gem_object_release(obj); + + kfree(etnaviv_obj); +} + +int etnaviv_gem_obj_add(struct drm_device *dev, struct drm_gem_object *obj) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + mutex_lock(&priv->gem_lock); + list_add_tail(&etnaviv_obj->gem_node, &priv->gem_list); + mutex_unlock(&priv->gem_lock); + + return 0; +} + +static int etnaviv_gem_new_impl(struct drm_device *dev, u32 size, u32 flags, + struct reservation_object *robj, const struct etnaviv_gem_ops *ops, + struct drm_gem_object **obj) +{ + struct etnaviv_gem_object *etnaviv_obj; + unsigned sz = sizeof(*etnaviv_obj); + bool valid = true; + + /* validate flags */ + switch (flags & ETNA_BO_CACHE_MASK) { + case ETNA_BO_UNCACHED: + case ETNA_BO_CACHED: + case ETNA_BO_WC: + break; + default: + valid = false; + } + + if (!valid) { + dev_err(dev->dev, "invalid cache flag: %x\n", + (flags & ETNA_BO_CACHE_MASK)); + return -EINVAL; + } + + etnaviv_obj = kzalloc(sz, GFP_KERNEL); + if (!etnaviv_obj) + return -ENOMEM; + + etnaviv_obj->flags = flags; + etnaviv_obj->ops = ops; + if (robj) { + etnaviv_obj->resv = robj; + } else { + etnaviv_obj->resv = &etnaviv_obj->_resv; + reservation_object_init(&etnaviv_obj->_resv); + } + + mutex_init(&etnaviv_obj->lock); + INIT_LIST_HEAD(&etnaviv_obj->vram_list); + + *obj = &etnaviv_obj->base; + + return 0; +} + +static struct drm_gem_object *__etnaviv_gem_new(struct drm_device *dev, + u32 size, u32 flags) +{ + struct drm_gem_object *obj = NULL; + int ret; + + size = PAGE_ALIGN(size); + + ret = etnaviv_gem_new_impl(dev, size, flags, NULL, + &etnaviv_gem_shmem_ops, &obj); + if (ret) + goto fail; + + ret = drm_gem_object_init(dev, obj, size); + if (ret == 0) { + struct address_space *mapping; + + /* + * Our buffers are kept pinned, so allocating them + * from the MOVABLE zone is a really bad idea, and + * conflicts with CMA. See coments above new_inode() + * why this is required _and_ expected if you're + * going to pin these pages. + */ + mapping = file_inode(obj->filp)->i_mapping; + mapping_set_gfp_mask(mapping, GFP_HIGHUSER); + } + + if (ret) + goto fail; + + return obj; + +fail: + if (obj) + drm_gem_object_unreference_unlocked(obj); + + return ERR_PTR(ret); +} + +/* convenience method to construct a GEM buffer object, and userspace handle */ +int etnaviv_gem_new_handle(struct drm_device *dev, struct drm_file *file, + u32 size, u32 flags, u32 *handle) +{ + struct drm_gem_object *obj; + int ret; + + obj = __etnaviv_gem_new(dev, size, flags); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + ret = etnaviv_gem_obj_add(dev, obj); + if (ret < 0) { + drm_gem_object_unreference_unlocked(obj); + return ret; + } + + ret = drm_gem_handle_create(file, obj, handle); + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +struct drm_gem_object *etnaviv_gem_new(struct drm_device *dev, + u32 size, u32 flags) +{ + struct drm_gem_object *obj; + int ret; + + obj = __etnaviv_gem_new(dev, size, flags); + if (IS_ERR(obj)) + return obj; + + ret = etnaviv_gem_obj_add(dev, obj); + if (ret < 0) { + drm_gem_object_unreference_unlocked(obj); + return ERR_PTR(ret); + } + + return obj; +} + +int etnaviv_gem_new_private(struct drm_device *dev, size_t size, u32 flags, + struct reservation_object *robj, const struct etnaviv_gem_ops *ops, + struct etnaviv_gem_object **res) +{ + struct drm_gem_object *obj; + int ret; + + ret = etnaviv_gem_new_impl(dev, size, flags, robj, ops, &obj); + if (ret) + return ret; + + drm_gem_private_object_init(dev, obj, size); + + *res = to_etnaviv_bo(obj); + + return 0; +} + +struct get_pages_work { + struct work_struct work; + struct mm_struct *mm; + struct task_struct *task; + struct etnaviv_gem_object *etnaviv_obj; +}; + +static struct page **etnaviv_gem_userptr_do_get_pages( + struct etnaviv_gem_object *etnaviv_obj, struct mm_struct *mm, struct task_struct *task) +{ + int ret = 0, pinned, npages = etnaviv_obj->base.size >> PAGE_SHIFT; + struct page **pvec; + uintptr_t ptr; + + pvec = drm_malloc_ab(npages, sizeof(struct page *)); + if (!pvec) + return ERR_PTR(-ENOMEM); + + pinned = 0; + ptr = etnaviv_obj->userptr.ptr; + + down_read(&mm->mmap_sem); + while (pinned < npages) { + ret = get_user_pages(task, mm, ptr, npages - pinned, + !etnaviv_obj->userptr.ro, 0, + pvec + pinned, NULL); + if (ret < 0) + break; + + ptr += ret * PAGE_SIZE; + pinned += ret; + } + up_read(&mm->mmap_sem); + + if (ret < 0) { + release_pages(pvec, pinned, 0); + drm_free_large(pvec); + return ERR_PTR(ret); + } + + return pvec; +} + +static void __etnaviv_gem_userptr_get_pages(struct work_struct *_work) +{ + struct get_pages_work *work = container_of(_work, typeof(*work), work); + struct etnaviv_gem_object *etnaviv_obj = work->etnaviv_obj; + struct page **pvec; + + pvec = etnaviv_gem_userptr_do_get_pages(etnaviv_obj, work->mm, work->task); + + mutex_lock(&etnaviv_obj->lock); + if (IS_ERR(pvec)) { + etnaviv_obj->userptr.work = ERR_CAST(pvec); + } else { + etnaviv_obj->userptr.work = NULL; + etnaviv_obj->pages = pvec; + } + + mutex_unlock(&etnaviv_obj->lock); + drm_gem_object_unreference_unlocked(&etnaviv_obj->base); + + mmput(work->mm); + put_task_struct(work->task); + kfree(work); +} + +static int etnaviv_gem_userptr_get_pages(struct etnaviv_gem_object *etnaviv_obj) +{ + struct page **pvec = NULL; + struct get_pages_work *work; + struct mm_struct *mm; + int ret, pinned, npages = etnaviv_obj->base.size >> PAGE_SHIFT; + + if (etnaviv_obj->userptr.work) { + if (IS_ERR(etnaviv_obj->userptr.work)) { + ret = PTR_ERR(etnaviv_obj->userptr.work); + etnaviv_obj->userptr.work = NULL; + } else { + ret = -EAGAIN; + } + return ret; + } + + mm = get_task_mm(etnaviv_obj->userptr.task); + pinned = 0; + if (mm == current->mm) { + pvec = drm_malloc_ab(npages, sizeof(struct page *)); + if (!pvec) { + mmput(mm); + return -ENOMEM; + } + + pinned = __get_user_pages_fast(etnaviv_obj->userptr.ptr, npages, + !etnaviv_obj->userptr.ro, pvec); + if (pinned < 0) { + drm_free_large(pvec); + mmput(mm); + return pinned; + } + + if (pinned == npages) { + etnaviv_obj->pages = pvec; + mmput(mm); + return 0; + } + } + + release_pages(pvec, pinned, 0); + drm_free_large(pvec); + + work = kmalloc(sizeof(*work), GFP_KERNEL); + if (!work) { + mmput(mm); + return -ENOMEM; + } + + get_task_struct(current); + drm_gem_object_reference(&etnaviv_obj->base); + + work->mm = mm; + work->task = current; + work->etnaviv_obj = etnaviv_obj; + + etnaviv_obj->userptr.work = &work->work; + INIT_WORK(&work->work, __etnaviv_gem_userptr_get_pages); + + etnaviv_queue_work(etnaviv_obj->base.dev, &work->work); + + return -EAGAIN; +} + +static void etnaviv_gem_userptr_release(struct etnaviv_gem_object *etnaviv_obj) +{ + if (etnaviv_obj->sgt) { + etnaviv_gem_scatterlist_unmap(etnaviv_obj); + sg_free_table(etnaviv_obj->sgt); + kfree(etnaviv_obj->sgt); + } + if (etnaviv_obj->pages) { + int npages = etnaviv_obj->base.size >> PAGE_SHIFT; + + release_pages(etnaviv_obj->pages, npages, 0); + drm_free_large(etnaviv_obj->pages); + } + put_task_struct(etnaviv_obj->userptr.task); +} + +static const struct etnaviv_gem_ops etnaviv_gem_userptr_ops = { + .get_pages = etnaviv_gem_userptr_get_pages, + .release = etnaviv_gem_userptr_release, + .vmap = etnaviv_gem_vmap_impl, +}; + +int etnaviv_gem_new_userptr(struct drm_device *dev, struct drm_file *file, + uintptr_t ptr, u32 size, u32 flags, u32 *handle) +{ + struct etnaviv_gem_object *etnaviv_obj; + int ret; + + ret = etnaviv_gem_new_private(dev, size, ETNA_BO_CACHED, NULL, + &etnaviv_gem_userptr_ops, &etnaviv_obj); + if (ret) + return ret; + + etnaviv_obj->userptr.ptr = ptr; + etnaviv_obj->userptr.task = current; + etnaviv_obj->userptr.ro = !(flags & ETNA_USERPTR_WRITE); + get_task_struct(current); + + ret = etnaviv_gem_obj_add(dev, &etnaviv_obj->base); + if (ret) { + drm_gem_object_unreference_unlocked(&etnaviv_obj->base); + return ret; + } + + ret = drm_gem_handle_create(file, &etnaviv_obj->base, handle); + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(&etnaviv_obj->base); + + return ret; +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gem.h b/drivers/gpu/drm/etnaviv/etnaviv_gem.h new file mode 100644 index 0000000..ab5df81 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_GEM_H__ +#define __ETNAVIV_GEM_H__ + +#include +#include "etnaviv_drv.h" + +struct etnaviv_gem_ops; +struct etnaviv_gem_object; + +struct etnaviv_gem_userptr { + uintptr_t ptr; + struct task_struct *task; + struct work_struct *work; + bool ro; +}; + +struct etnaviv_vram_mapping { + struct list_head obj_node; + struct list_head scan_node; + struct list_head mmu_node; + struct etnaviv_gem_object *object; + struct etnaviv_iommu *mmu; + struct drm_mm_node vram_node; + unsigned int use; + u32 iova; +}; + +struct etnaviv_gem_object { + struct drm_gem_object base; + const struct etnaviv_gem_ops *ops; + struct mutex lock; + + u32 flags; + + struct list_head gem_node; + struct etnaviv_gpu *gpu; /* non-null if active */ + atomic_t gpu_active; + u32 access; + + struct page **pages; + struct sg_table *sgt; + void *vaddr; + + /* normally (resv == &_resv) except for imported bo's */ + struct reservation_object *resv; + struct reservation_object _resv; + + struct list_head vram_list; + + /* cache maintenance */ + u32 last_cpu_prep_op; + + struct etnaviv_gem_userptr userptr; +}; + +static inline +struct etnaviv_gem_object *to_etnaviv_bo(struct drm_gem_object *obj) +{ + return container_of(obj, struct etnaviv_gem_object, base); +} + +struct etnaviv_gem_ops { + int (*get_pages)(struct etnaviv_gem_object *); + void (*release)(struct etnaviv_gem_object *); + void *(*vmap)(struct etnaviv_gem_object *); +}; + +static inline bool is_active(struct etnaviv_gem_object *etnaviv_obj) +{ + return atomic_read(&etnaviv_obj->gpu_active) != 0; +} + +#define MAX_CMDS 4 + +/* Created per submit-ioctl, to track bo's and cmdstream bufs, etc, + * associated with the cmdstream submission for synchronization (and + * make it easier to unwind when things go wrong, etc). This only + * lasts for the duration of the submit-ioctl. + */ +struct etnaviv_gem_submit { + struct drm_device *dev; + struct etnaviv_gpu *gpu; + struct ww_acquire_ctx ticket; + u32 fence; + unsigned int nr_bos; + struct { + u32 flags; + struct etnaviv_gem_object *obj; + u32 iova; + } bos[0]; +}; + +int etnaviv_gem_wait_bo(struct etnaviv_gpu *gpu, struct drm_gem_object *obj, + struct timespec *timeout); +int etnaviv_gem_new_private(struct drm_device *dev, size_t size, u32 flags, + struct reservation_object *robj, const struct etnaviv_gem_ops *ops, + struct etnaviv_gem_object **res); +int etnaviv_gem_obj_add(struct drm_device *dev, struct drm_gem_object *obj); +struct page **etnaviv_gem_get_pages(struct etnaviv_gem_object *obj); +void etnaviv_gem_put_pages(struct etnaviv_gem_object *obj); + +#endif /* __ETNAVIV_GEM_H__ */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gem_prime.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_prime.c new file mode 100644 index 0000000..4e67395 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_prime.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include "etnaviv_drv.h" +#include "etnaviv_gem.h" + + +struct sg_table *etnaviv_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + BUG_ON(!etnaviv_obj->sgt); /* should have already pinned! */ + + return etnaviv_obj->sgt; +} + +void *etnaviv_gem_prime_vmap(struct drm_gem_object *obj) +{ + return etnaviv_gem_vmap(obj); +} + +void etnaviv_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr) +{ + /* TODO msm_gem_vunmap() */ +} + +int etnaviv_gem_prime_pin(struct drm_gem_object *obj) +{ + if (!obj->import_attach) { + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + mutex_lock(&etnaviv_obj->lock); + etnaviv_gem_get_pages(etnaviv_obj); + mutex_unlock(&etnaviv_obj->lock); + } + return 0; +} + +void etnaviv_gem_prime_unpin(struct drm_gem_object *obj) +{ + if (!obj->import_attach) { + struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj); + + mutex_lock(&etnaviv_obj->lock); + etnaviv_gem_put_pages(to_etnaviv_bo(obj)); + mutex_unlock(&etnaviv_obj->lock); + } +} + +static void etnaviv_gem_prime_release(struct etnaviv_gem_object *etnaviv_obj) +{ + if (etnaviv_obj->vaddr) + dma_buf_vunmap(etnaviv_obj->base.import_attach->dmabuf, + etnaviv_obj->vaddr); + + /* Don't drop the pages for imported dmabuf, as they are not + * ours, just free the array we allocated: + */ + if (etnaviv_obj->pages) + drm_free_large(etnaviv_obj->pages); + + drm_prime_gem_destroy(&etnaviv_obj->base, etnaviv_obj->sgt); +} + +static void *etnaviv_gem_prime_vmap_impl(struct etnaviv_gem_object *etnaviv_obj) +{ + lockdep_assert_held(&etnaviv_obj->lock); + + return dma_buf_vmap(etnaviv_obj->base.import_attach->dmabuf); +} + +static const struct etnaviv_gem_ops etnaviv_gem_prime_ops = { + /* .get_pages should never be called */ + .release = etnaviv_gem_prime_release, + .vmap = etnaviv_gem_prime_vmap_impl, +}; + +struct drm_gem_object *etnaviv_gem_prime_import_sg_table(struct drm_device *dev, + struct dma_buf_attachment *attach, struct sg_table *sgt) +{ + struct etnaviv_gem_object *etnaviv_obj; + size_t size = PAGE_ALIGN(attach->dmabuf->size); + int ret, npages; + + ret = etnaviv_gem_new_private(dev, size, ETNA_BO_WC, + attach->dmabuf->resv, + &etnaviv_gem_prime_ops, &etnaviv_obj); + if (ret < 0) + return ERR_PTR(ret); + + npages = size / PAGE_SIZE; + + etnaviv_obj->sgt = sgt; + etnaviv_obj->pages = drm_malloc_ab(npages, sizeof(struct page *)); + if (!etnaviv_obj->pages) { + ret = -ENOMEM; + goto fail; + } + + ret = drm_prime_sg_to_page_addr_arrays(sgt, etnaviv_obj->pages, + NULL, npages); + if (ret) + goto fail; + + ret = etnaviv_gem_obj_add(dev, &etnaviv_obj->base); + if (ret) + goto fail; + + return &etnaviv_obj->base; + +fail: + drm_gem_object_unreference_unlocked(&etnaviv_obj->base); + + return ERR_PTR(ret); +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c new file mode 100644 index 0000000..1aba01a --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include "etnaviv_drv.h" +#include "etnaviv_gpu.h" +#include "etnaviv_gem.h" + +/* + * Cmdstream submission: + */ + +#define BO_INVALID_FLAGS ~(ETNA_SUBMIT_BO_READ | ETNA_SUBMIT_BO_WRITE) +/* make sure these don't conflict w/ ETNAVIV_SUBMIT_BO_x */ +#define BO_LOCKED 0x4000 +#define BO_PINNED 0x2000 + +static inline void __user *to_user_ptr(u64 address) +{ + return (void __user *)(uintptr_t)address; +} + +static struct etnaviv_gem_submit *submit_create(struct drm_device *dev, + struct etnaviv_gpu *gpu, size_t nr) +{ + struct etnaviv_gem_submit *submit; + size_t sz = size_vstruct(nr, sizeof(submit->bos[0]), sizeof(*submit)); + + submit = kmalloc(sz, GFP_TEMPORARY | __GFP_NOWARN | __GFP_NORETRY); + if (submit) { + submit->dev = dev; + submit->gpu = gpu; + + /* initially, until copy_from_user() and bo lookup succeeds: */ + submit->nr_bos = 0; + + ww_acquire_init(&submit->ticket, &reservation_ww_class); + } + + return submit; +} + +static int submit_lookup_objects(struct etnaviv_gem_submit *submit, + struct drm_file *file, struct drm_etnaviv_gem_submit_bo *submit_bos, + unsigned nr_bos) +{ + struct drm_etnaviv_gem_submit_bo *bo; + unsigned i; + int ret = 0; + + spin_lock(&file->table_lock); + + for (i = 0, bo = submit_bos; i < nr_bos; i++, bo++) { + struct drm_gem_object *obj; + + if (bo->flags & BO_INVALID_FLAGS) { + DRM_ERROR("invalid flags: %x\n", bo->flags); + ret = -EINVAL; + goto out_unlock; + } + + submit->bos[i].flags = bo->flags; + + /* normally use drm_gem_object_lookup(), but for bulk lookup + * all under single table_lock just hit object_idr directly: + */ + obj = idr_find(&file->object_idr, bo->handle); + if (!obj) { + DRM_ERROR("invalid handle %u at index %u\n", + bo->handle, i); + ret = -EINVAL; + goto out_unlock; + } + + /* + * Take a refcount on the object. The file table lock + * prevents the object_idr's refcount on this being dropped. + */ + drm_gem_object_reference(obj); + + submit->bos[i].obj = to_etnaviv_bo(obj); + } + +out_unlock: + submit->nr_bos = i; + spin_unlock(&file->table_lock); + + return ret; +} + +static void submit_unlock_object(struct etnaviv_gem_submit *submit, int i) +{ + if (submit->bos[i].flags & BO_LOCKED) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + + ww_mutex_unlock(&etnaviv_obj->resv->lock); + submit->bos[i].flags &= ~BO_LOCKED; + } +} + +static int submit_lock_objects(struct etnaviv_gem_submit *submit) +{ + int contended, slow_locked = -1, i, ret = 0; + +retry: + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + + if (slow_locked == i) + slow_locked = -1; + + contended = i; + + if (!(submit->bos[i].flags & BO_LOCKED)) { + ret = ww_mutex_lock_interruptible(&etnaviv_obj->resv->lock, + &submit->ticket); + if (ret == -EALREADY) + DRM_ERROR("BO at index %u already on submit list\n", + i); + if (ret) + goto fail; + submit->bos[i].flags |= BO_LOCKED; + } + } + + ww_acquire_done(&submit->ticket); + + return 0; + +fail: + for (; i >= 0; i--) + submit_unlock_object(submit, i); + + if (slow_locked > 0) + submit_unlock_object(submit, slow_locked); + + if (ret == -EDEADLK) { + struct etnaviv_gem_object *etnaviv_obj; + + etnaviv_obj = submit->bos[contended].obj; + + /* we lost out in a seqno race, lock and retry.. */ + ret = ww_mutex_lock_slow_interruptible(&etnaviv_obj->resv->lock, + &submit->ticket); + if (!ret) { + submit->bos[contended].flags |= BO_LOCKED; + slow_locked = contended; + goto retry; + } + } + + return ret; +} + +static int submit_fence_sync(const struct etnaviv_gem_submit *submit) +{ + unsigned int context = submit->gpu->fence_context; + int i, ret = 0; + + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + bool write = submit->bos[i].flags & ETNA_SUBMIT_BO_WRITE; + + ret = etnaviv_gpu_fence_sync_obj(etnaviv_obj, context, write); + if (ret) + break; + } + + return ret; +} + +static void submit_unpin_objects(struct etnaviv_gem_submit *submit) +{ + int i; + + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + + if (submit->bos[i].flags & BO_PINNED) + etnaviv_gem_put_iova(submit->gpu, &etnaviv_obj->base); + + submit->bos[i].iova = 0; + submit->bos[i].flags &= ~BO_PINNED; + } +} + +static int submit_pin_objects(struct etnaviv_gem_submit *submit) +{ + int i, ret = 0; + + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + u32 iova; + + ret = etnaviv_gem_get_iova(submit->gpu, &etnaviv_obj->base, + &iova); + if (ret) + break; + + submit->bos[i].flags |= BO_PINNED; + submit->bos[i].iova = iova; + } + + return ret; +} + +static int submit_bo(struct etnaviv_gem_submit *submit, u32 idx, + struct etnaviv_gem_object **obj, u32 *iova) +{ + if (idx >= submit->nr_bos) { + DRM_ERROR("invalid buffer index: %u (out of %u)\n", + idx, submit->nr_bos); + return -EINVAL; + } + + if (obj) + *obj = submit->bos[idx].obj; + if (iova) + *iova = submit->bos[idx].iova; + + return 0; +} + +/* process the reloc's and patch up the cmdstream as needed: */ +static int submit_reloc(struct etnaviv_gem_submit *submit, void *stream, + u32 size, const struct drm_etnaviv_gem_submit_reloc *relocs, + u32 nr_relocs) +{ + u32 i, last_offset = 0; + u32 *ptr = stream; + int ret; + + for (i = 0; i < nr_relocs; i++) { + const struct drm_etnaviv_gem_submit_reloc *r = relocs + i; + struct etnaviv_gem_object *bobj; + u32 iova, off; + + if (unlikely(r->flags)) { + DRM_ERROR("invalid reloc flags\n"); + return -EINVAL; + } + + if (r->submit_offset % 4) { + DRM_ERROR("non-aligned reloc offset: %u\n", + r->submit_offset); + return -EINVAL; + } + + /* offset in dwords: */ + off = r->submit_offset / 4; + + if ((off >= size ) || + (off < last_offset)) { + DRM_ERROR("invalid offset %u at reloc %u\n", off, i); + return -EINVAL; + } + + ret = submit_bo(submit, r->reloc_idx, &bobj, &iova); + if (ret) + return ret; + + if (r->reloc_offset >= + bobj->base.size - sizeof(*ptr)) { + DRM_ERROR("relocation %u outside object", i); + return -EINVAL; + } + + ptr[off] = iova + r->reloc_offset; + + last_offset = off; + } + + return 0; +} + +static void submit_cleanup(struct etnaviv_gem_submit *submit) +{ + unsigned i; + + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + + submit_unlock_object(submit, i); + drm_gem_object_unreference_unlocked(&etnaviv_obj->base); + } + + ww_acquire_fini(&submit->ticket); + kfree(submit); +} + +int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data, + struct drm_file *file) +{ + struct etnaviv_drm_private *priv = dev->dev_private; + struct drm_etnaviv_gem_submit *args = data; + struct drm_etnaviv_gem_submit_reloc *relocs; + struct drm_etnaviv_gem_submit_bo *bos; + struct etnaviv_gem_submit *submit; + struct etnaviv_cmdbuf *cmdbuf; + struct etnaviv_gpu *gpu; + void *stream; + int ret; + + if (args->pipe >= ETNA_MAX_PIPES) + return -EINVAL; + + gpu = priv->gpu[args->pipe]; + if (!gpu) + return -ENXIO; + + if (args->stream_size % 4) { + DRM_ERROR("non-aligned cmdstream buffer size: %u\n", + args->stream_size); + return -EINVAL; + } + + if (args->exec_state != ETNA_PIPE_3D && + args->exec_state != ETNA_PIPE_2D && + args->exec_state != ETNA_PIPE_VG) { + DRM_ERROR("invalid exec_state: 0x%x\n", args->exec_state); + return -EINVAL; + } + + /* + * Copy the command submission and bo array to kernel space in + * one go, and do this outside of any locks. + */ + bos = drm_malloc_ab(args->nr_bos, sizeof(*bos)); + relocs = drm_malloc_ab(args->nr_relocs, sizeof(*relocs)); + stream = drm_malloc_ab(1, args->stream_size); + cmdbuf = etnaviv_gpu_cmdbuf_new(gpu, ALIGN(args->stream_size, 8) + 8, + args->nr_bos); + if (!bos || !relocs || !stream || !cmdbuf) { + ret = -ENOMEM; + goto err_submit_cmds; + } + + cmdbuf->exec_state = args->exec_state; + cmdbuf->ctx = file->driver_priv; + + ret = copy_from_user(bos, to_user_ptr(args->bos), + args->nr_bos * sizeof(*bos)); + if (ret) { + ret = -EFAULT; + goto err_submit_cmds; + } + + ret = copy_from_user(relocs, to_user_ptr(args->relocs), + args->nr_relocs * sizeof(*relocs)); + if (ret) { + ret = -EFAULT; + goto err_submit_cmds; + } + + ret = copy_from_user(stream, to_user_ptr(args->stream), + args->stream_size); + if (ret) { + ret = -EFAULT; + goto err_submit_cmds; + } + + submit = submit_create(dev, gpu, args->nr_bos); + if (!submit) { + ret = -ENOMEM; + goto err_submit_cmds; + } + + ret = submit_lookup_objects(submit, file, bos, args->nr_bos); + if (ret) + goto err_submit_objects; + + ret = submit_lock_objects(submit); + if (ret) + goto err_submit_objects; + + if (!etnaviv_cmd_validate_one(gpu, stream, args->stream_size / 4, + relocs, args->nr_relocs)) { + ret = -EINVAL; + goto err_submit_objects; + } + + ret = submit_fence_sync(submit); + if (ret) + goto err_submit_objects; + + ret = submit_pin_objects(submit); + if (ret) + goto out; + + ret = submit_reloc(submit, stream, args->stream_size / 4, + relocs, args->nr_relocs); + if (ret) + goto out; + + memcpy(cmdbuf->vaddr, stream, args->stream_size); + cmdbuf->user_size = ALIGN(args->stream_size, 8); + + ret = etnaviv_gpu_submit(gpu, submit, cmdbuf); + if (ret == 0) + cmdbuf = NULL; + + args->fence = submit->fence; + +out: + submit_unpin_objects(submit); + + /* + * If we're returning -EAGAIN, it may be due to the userptr code + * wanting to run its workqueue outside of any locks. Flush our + * workqueue to ensure that it is run in a timely manner. + */ + if (ret == -EAGAIN) + flush_workqueue(priv->wq); + +err_submit_objects: + submit_cleanup(submit); + +err_submit_cmds: + /* if we still own the cmdbuf */ + if (cmdbuf) + etnaviv_gpu_cmdbuf_free(cmdbuf); + if (stream) + drm_free_large(stream); + if (bos) + drm_free_large(bos); + if (relocs) + drm_free_large(relocs); + + return ret; +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c new file mode 100644 index 0000000..a33162c --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.c @@ -0,0 +1,1712 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include "etnaviv_dump.h" +#include "etnaviv_gpu.h" +#include "etnaviv_gem.h" +#include "etnaviv_mmu.h" +#include "etnaviv_iommu.h" +#include "etnaviv_iommu_v2.h" +#include "common.xml.h" +#include "state.xml.h" +#include "state_hi.xml.h" +#include "cmdstream.xml.h" + +static const struct platform_device_id gpu_ids[] = { + { .name = "etnaviv-gpu,2d" }, + { }, +}; + +static bool etnaviv_dump_core = true; +module_param_named(dump_core, etnaviv_dump_core, bool, 0600); + +/* + * Driver functions: + */ + +int etnaviv_gpu_get_param(struct etnaviv_gpu *gpu, u32 param, u64 *value) +{ + switch (param) { + case ETNAVIV_PARAM_GPU_MODEL: + *value = gpu->identity.model; + break; + + case ETNAVIV_PARAM_GPU_REVISION: + *value = gpu->identity.revision; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_0: + *value = gpu->identity.features; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_1: + *value = gpu->identity.minor_features0; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_2: + *value = gpu->identity.minor_features1; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_3: + *value = gpu->identity.minor_features2; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_4: + *value = gpu->identity.minor_features3; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_5: + *value = gpu->identity.minor_features4; + break; + + case ETNAVIV_PARAM_GPU_FEATURES_6: + *value = gpu->identity.minor_features5; + break; + + case ETNAVIV_PARAM_GPU_STREAM_COUNT: + *value = gpu->identity.stream_count; + break; + + case ETNAVIV_PARAM_GPU_REGISTER_MAX: + *value = gpu->identity.register_max; + break; + + case ETNAVIV_PARAM_GPU_THREAD_COUNT: + *value = gpu->identity.thread_count; + break; + + case ETNAVIV_PARAM_GPU_VERTEX_CACHE_SIZE: + *value = gpu->identity.vertex_cache_size; + break; + + case ETNAVIV_PARAM_GPU_SHADER_CORE_COUNT: + *value = gpu->identity.shader_core_count; + break; + + case ETNAVIV_PARAM_GPU_PIXEL_PIPES: + *value = gpu->identity.pixel_pipes; + break; + + case ETNAVIV_PARAM_GPU_VERTEX_OUTPUT_BUFFER_SIZE: + *value = gpu->identity.vertex_output_buffer_size; + break; + + case ETNAVIV_PARAM_GPU_BUFFER_SIZE: + *value = gpu->identity.buffer_size; + break; + + case ETNAVIV_PARAM_GPU_INSTRUCTION_COUNT: + *value = gpu->identity.instruction_count; + break; + + case ETNAVIV_PARAM_GPU_NUM_CONSTANTS: + *value = gpu->identity.num_constants; + break; + + case ETNAVIV_PARAM_GPU_NUM_VARYINGS: + *value = gpu->identity.varyings_count; + break; + + default: + DBG("%s: invalid param: %u", dev_name(gpu->dev), param); + return -EINVAL; + } + + return 0; +} + + +#define etnaviv_is_model_rev(gpu, mod, rev) \ + ((gpu)->identity.model == chipModel_##mod && \ + (gpu)->identity.revision == rev) +#define etnaviv_field(val, field) \ + (((val) & field##__MASK) >> field##__SHIFT) + +static void etnaviv_hw_specs(struct etnaviv_gpu *gpu) +{ + if (gpu->identity.minor_features0 & + chipMinorFeatures0_MORE_MINOR_FEATURES) { + u32 specs[4]; + unsigned int streams; + + specs[0] = gpu_read(gpu, VIVS_HI_CHIP_SPECS); + specs[1] = gpu_read(gpu, VIVS_HI_CHIP_SPECS_2); + specs[2] = gpu_read(gpu, VIVS_HI_CHIP_SPECS_3); + specs[3] = gpu_read(gpu, VIVS_HI_CHIP_SPECS_4); + + gpu->identity.stream_count = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_STREAM_COUNT); + gpu->identity.register_max = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_REGISTER_MAX); + gpu->identity.thread_count = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_THREAD_COUNT); + gpu->identity.vertex_cache_size = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE); + gpu->identity.shader_core_count = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT); + gpu->identity.pixel_pipes = etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_PIXEL_PIPES); + gpu->identity.vertex_output_buffer_size = + etnaviv_field(specs[0], + VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE); + + gpu->identity.buffer_size = etnaviv_field(specs[1], + VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE); + gpu->identity.instruction_count = etnaviv_field(specs[1], + VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT); + gpu->identity.num_constants = etnaviv_field(specs[1], + VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS); + + gpu->identity.varyings_count = etnaviv_field(specs[2], + VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT); + + /* This overrides the value from older register if non-zero */ + streams = etnaviv_field(specs[3], + VIVS_HI_CHIP_SPECS_4_STREAM_COUNT); + if (streams) + gpu->identity.stream_count = streams; + } + + /* Fill in the stream count if not specified */ + if (gpu->identity.stream_count == 0) { + if (gpu->identity.model >= 0x1000) + gpu->identity.stream_count = 4; + else + gpu->identity.stream_count = 1; + } + + /* Convert the register max value */ + if (gpu->identity.register_max) + gpu->identity.register_max = 1 << gpu->identity.register_max; + else if (gpu->identity.model == chipModel_GC400) + gpu->identity.register_max = 32; + else + gpu->identity.register_max = 64; + + /* Convert thread count */ + if (gpu->identity.thread_count) + gpu->identity.thread_count = 1 << gpu->identity.thread_count; + else if (gpu->identity.model == chipModel_GC400) + gpu->identity.thread_count = 64; + else if (gpu->identity.model == chipModel_GC500 || + gpu->identity.model == chipModel_GC530) + gpu->identity.thread_count = 128; + else + gpu->identity.thread_count = 256; + + if (gpu->identity.vertex_cache_size == 0) + gpu->identity.vertex_cache_size = 8; + + if (gpu->identity.shader_core_count == 0) { + if (gpu->identity.model >= 0x1000) + gpu->identity.shader_core_count = 2; + else + gpu->identity.shader_core_count = 1; + } + + if (gpu->identity.pixel_pipes == 0) + gpu->identity.pixel_pipes = 1; + + /* Convert virtex buffer size */ + if (gpu->identity.vertex_output_buffer_size) { + gpu->identity.vertex_output_buffer_size = + 1 << gpu->identity.vertex_output_buffer_size; + } else if (gpu->identity.model == chipModel_GC400) { + if (gpu->identity.revision < 0x4000) + gpu->identity.vertex_output_buffer_size = 512; + else if (gpu->identity.revision < 0x4200) + gpu->identity.vertex_output_buffer_size = 256; + else + gpu->identity.vertex_output_buffer_size = 128; + } else { + gpu->identity.vertex_output_buffer_size = 512; + } + + switch (gpu->identity.instruction_count) { + case 0: + if (etnaviv_is_model_rev(gpu, GC2000, 0x5108) || + gpu->identity.model == chipModel_GC880) + gpu->identity.instruction_count = 512; + else + gpu->identity.instruction_count = 256; + break; + + case 1: + gpu->identity.instruction_count = 1024; + break; + + case 2: + gpu->identity.instruction_count = 2048; + break; + + default: + gpu->identity.instruction_count = 256; + break; + } + + if (gpu->identity.num_constants == 0) + gpu->identity.num_constants = 168; + + if (gpu->identity.varyings_count == 0) { + if (gpu->identity.minor_features1 & chipMinorFeatures1_HALTI0) + gpu->identity.varyings_count = 12; + else + gpu->identity.varyings_count = 8; + } + + /* + * For some cores, two varyings are consumed for position, so the + * maximum varying count needs to be reduced by one. + */ + if (etnaviv_is_model_rev(gpu, GC5000, 0x5434) || + etnaviv_is_model_rev(gpu, GC4000, 0x5222) || + etnaviv_is_model_rev(gpu, GC4000, 0x5245) || + etnaviv_is_model_rev(gpu, GC4000, 0x5208) || + etnaviv_is_model_rev(gpu, GC3000, 0x5435) || + etnaviv_is_model_rev(gpu, GC2200, 0x5244) || + etnaviv_is_model_rev(gpu, GC2100, 0x5108) || + etnaviv_is_model_rev(gpu, GC2000, 0x5108) || + etnaviv_is_model_rev(gpu, GC1500, 0x5246) || + etnaviv_is_model_rev(gpu, GC880, 0x5107) || + etnaviv_is_model_rev(gpu, GC880, 0x5106)) + gpu->identity.varyings_count -= 1; +} + +static void etnaviv_hw_identify(struct etnaviv_gpu *gpu) +{ + u32 chipIdentity; + + chipIdentity = gpu_read(gpu, VIVS_HI_CHIP_IDENTITY); + + /* Special case for older graphic cores. */ + if (etnaviv_field(chipIdentity, VIVS_HI_CHIP_IDENTITY_FAMILY) == 0x01) { + gpu->identity.model = chipModel_GC500; + gpu->identity.revision = etnaviv_field(chipIdentity, + VIVS_HI_CHIP_IDENTITY_REVISION); + } else { + + gpu->identity.model = gpu_read(gpu, VIVS_HI_CHIP_MODEL); + gpu->identity.revision = gpu_read(gpu, VIVS_HI_CHIP_REV); + + /* + * !!!! HACK ALERT !!!! + * Because people change device IDs without letting software + * know about it - here is the hack to make it all look the + * same. Only for GC400 family. + */ + if ((gpu->identity.model & 0xff00) == 0x0400 && + gpu->identity.model != chipModel_GC420) { + gpu->identity.model = gpu->identity.model & 0x0400; + } + + /* Another special case */ + if (etnaviv_is_model_rev(gpu, GC300, 0x2201)) { + u32 chipDate = gpu_read(gpu, VIVS_HI_CHIP_DATE); + u32 chipTime = gpu_read(gpu, VIVS_HI_CHIP_TIME); + + if (chipDate == 0x20080814 && chipTime == 0x12051100) { + /* + * This IP has an ECO; put the correct + * revision in it. + */ + gpu->identity.revision = 0x1051; + } + } + } + + dev_info(gpu->dev, "model: GC%x, revision: %x\n", + gpu->identity.model, gpu->identity.revision); + + gpu->identity.features = gpu_read(gpu, VIVS_HI_CHIP_FEATURE); + + /* Disable fast clear on GC700. */ + if (gpu->identity.model == chipModel_GC700) + gpu->identity.features &= ~chipFeatures_FAST_CLEAR; + + if ((gpu->identity.model == chipModel_GC500 && + gpu->identity.revision < 2) || + (gpu->identity.model == chipModel_GC300 && + gpu->identity.revision < 0x2000)) { + + /* + * GC500 rev 1.x and GC300 rev < 2.0 doesn't have these + * registers. + */ + gpu->identity.minor_features0 = 0; + gpu->identity.minor_features1 = 0; + gpu->identity.minor_features2 = 0; + gpu->identity.minor_features3 = 0; + gpu->identity.minor_features4 = 0; + gpu->identity.minor_features5 = 0; + } else + gpu->identity.minor_features0 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_0); + + if (gpu->identity.minor_features0 & + chipMinorFeatures0_MORE_MINOR_FEATURES) { + gpu->identity.minor_features1 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_1); + gpu->identity.minor_features2 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_2); + gpu->identity.minor_features3 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_3); + gpu->identity.minor_features4 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_4); + gpu->identity.minor_features5 = + gpu_read(gpu, VIVS_HI_CHIP_MINOR_FEATURE_5); + } + + /* GC600 idle register reports zero bits where modules aren't present */ + if (gpu->identity.model == chipModel_GC600) { + gpu->idle_mask = VIVS_HI_IDLE_STATE_TX | + VIVS_HI_IDLE_STATE_RA | + VIVS_HI_IDLE_STATE_SE | + VIVS_HI_IDLE_STATE_PA | + VIVS_HI_IDLE_STATE_SH | + VIVS_HI_IDLE_STATE_PE | + VIVS_HI_IDLE_STATE_DE | + VIVS_HI_IDLE_STATE_FE; + } else { + gpu->idle_mask = ~VIVS_HI_IDLE_STATE_AXI_LP; + } + + etnaviv_hw_specs(gpu); +} + +static void etnaviv_gpu_load_clock(struct etnaviv_gpu *gpu, u32 clock) +{ + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, clock | + VIVS_HI_CLOCK_CONTROL_FSCALE_CMD_LOAD); + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, clock); +} + +static int etnaviv_hw_reset(struct etnaviv_gpu *gpu) +{ + u32 control, idle; + unsigned long timeout; + bool failed = true; + + /* TODO + * + * - clock gating + * - puls eater + * - what about VG? + */ + + /* We hope that the GPU resets in under one second */ + timeout = jiffies + msecs_to_jiffies(1000); + + while (time_is_after_jiffies(timeout)) { + control = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS | + VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40); + + /* enable clock */ + etnaviv_gpu_load_clock(gpu, control); + + /* Wait for stable clock. Vivante's code waited for 1ms */ + usleep_range(1000, 10000); + + /* isolate the GPU. */ + control |= VIVS_HI_CLOCK_CONTROL_ISOLATE_GPU; + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, control); + + /* set soft reset. */ + control |= VIVS_HI_CLOCK_CONTROL_SOFT_RESET; + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, control); + + /* wait for reset. */ + msleep(1); + + /* reset soft reset bit. */ + control &= ~VIVS_HI_CLOCK_CONTROL_SOFT_RESET; + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, control); + + /* reset GPU isolation. */ + control &= ~VIVS_HI_CLOCK_CONTROL_ISOLATE_GPU; + gpu_write(gpu, VIVS_HI_CLOCK_CONTROL, control); + + /* read idle register. */ + idle = gpu_read(gpu, VIVS_HI_IDLE_STATE); + + /* try reseting again if FE it not idle */ + if ((idle & VIVS_HI_IDLE_STATE_FE) == 0) { + dev_dbg(gpu->dev, "FE is not idle\n"); + continue; + } + + /* read reset register. */ + control = gpu_read(gpu, VIVS_HI_CLOCK_CONTROL); + + /* is the GPU idle? */ + if (((control & VIVS_HI_CLOCK_CONTROL_IDLE_3D) == 0) || + ((control & VIVS_HI_CLOCK_CONTROL_IDLE_2D) == 0)) { + dev_dbg(gpu->dev, "GPU is not idle\n"); + continue; + } + + failed = false; + break; + } + + if (failed) { + idle = gpu_read(gpu, VIVS_HI_IDLE_STATE); + control = gpu_read(gpu, VIVS_HI_CLOCK_CONTROL); + + dev_err(gpu->dev, "GPU failed to reset: FE %sidle, 3D %sidle, 2D %sidle\n", + idle & VIVS_HI_IDLE_STATE_FE ? "" : "not ", + control & VIVS_HI_CLOCK_CONTROL_IDLE_3D ? "" : "not ", + control & VIVS_HI_CLOCK_CONTROL_IDLE_2D ? "" : "not "); + + return -EBUSY; + } + + /* We rely on the GPU running, so program the clock */ + control = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS | + VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40); + + /* enable clock */ + etnaviv_gpu_load_clock(gpu, control); + + return 0; +} + +static void etnaviv_gpu_hw_init(struct etnaviv_gpu *gpu) +{ + u16 prefetch; + + if ((etnaviv_is_model_rev(gpu, GC320, 0x5007) || + etnaviv_is_model_rev(gpu, GC320, 0x5220)) && + gpu_read(gpu, VIVS_HI_CHIP_TIME) != 0x2062400) { + u32 mc_memory_debug; + + mc_memory_debug = gpu_read(gpu, VIVS_MC_DEBUG_MEMORY) & ~0xff; + + if (gpu->identity.revision == 0x5007) + mc_memory_debug |= 0x0c; + else + mc_memory_debug |= 0x08; + + gpu_write(gpu, VIVS_MC_DEBUG_MEMORY, mc_memory_debug); + } + + /* + * Update GPU AXI cache atttribute to "cacheable, no allocate". + * This is necessary to prevent the iMX6 SoC locking up. + */ + gpu_write(gpu, VIVS_HI_AXI_CONFIG, + VIVS_HI_AXI_CONFIG_AWCACHE(2) | + VIVS_HI_AXI_CONFIG_ARCACHE(2)); + + /* GC2000 rev 5108 needs a special bus config */ + if (etnaviv_is_model_rev(gpu, GC2000, 0x5108)) { + u32 bus_config = gpu_read(gpu, VIVS_MC_BUS_CONFIG); + bus_config &= ~(VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG__MASK | + VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG__MASK); + bus_config |= VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG(1) | + VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG(0); + gpu_write(gpu, VIVS_MC_BUS_CONFIG, bus_config); + } + + /* set base addresses */ + gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_RA, gpu->memory_base); + gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_FE, gpu->memory_base); + gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_TX, gpu->memory_base); + gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_PEZ, gpu->memory_base); + gpu_write(gpu, VIVS_MC_MEMORY_BASE_ADDR_PE, gpu->memory_base); + + /* setup the MMU page table pointers */ + etnaviv_iommu_domain_restore(gpu, gpu->mmu->domain); + + /* Start command processor */ + prefetch = etnaviv_buffer_init(gpu); + + gpu_write(gpu, VIVS_HI_INTR_ENBL, ~0U); + gpu_write(gpu, VIVS_FE_COMMAND_ADDRESS, + gpu->buffer->paddr - gpu->memory_base); + gpu_write(gpu, VIVS_FE_COMMAND_CONTROL, + VIVS_FE_COMMAND_CONTROL_ENABLE | + VIVS_FE_COMMAND_CONTROL_PREFETCH(prefetch)); +} + +int etnaviv_gpu_init(struct etnaviv_gpu *gpu) +{ + int ret, i; + struct iommu_domain *iommu; + enum etnaviv_iommu_version version; + bool mmuv2; + + ret = pm_runtime_get_sync(gpu->dev); + if (ret < 0) + return ret; + + etnaviv_hw_identify(gpu); + + if (gpu->identity.model == 0) { + dev_err(gpu->dev, "Unknown GPU model\n"); + ret = -ENXIO; + goto fail; + } + + /* Exclude VG cores with FE2.0 */ + if (gpu->identity.features & chipFeatures_PIPE_VG && + gpu->identity.features & chipFeatures_FE20) { + dev_info(gpu->dev, "Ignoring GPU with VG and FE2.0\n"); + ret = -ENXIO; + goto fail; + } + + ret = etnaviv_hw_reset(gpu); + if (ret) + goto fail; + + /* Setup IOMMU.. eventually we will (I think) do this once per context + * and have separate page tables per context. For now, to keep things + * simple and to get something working, just use a single address space: + */ + mmuv2 = gpu->identity.minor_features1 & chipMinorFeatures1_MMU_VERSION; + dev_dbg(gpu->dev, "mmuv2: %d\n", mmuv2); + + if (!mmuv2) { + iommu = etnaviv_iommu_domain_alloc(gpu); + version = ETNAVIV_IOMMU_V1; + } else { + iommu = etnaviv_iommu_v2_domain_alloc(gpu); + version = ETNAVIV_IOMMU_V2; + } + + if (!iommu) { + ret = -ENOMEM; + goto fail; + } + + gpu->mmu = etnaviv_iommu_new(gpu, iommu, version); + if (!gpu->mmu) { + iommu_domain_free(iommu); + ret = -ENOMEM; + goto fail; + } + + /* Create buffer: */ + gpu->buffer = etnaviv_gpu_cmdbuf_new(gpu, PAGE_SIZE, 0); + if (!gpu->buffer) { + ret = -ENOMEM; + dev_err(gpu->dev, "could not create command buffer\n"); + goto destroy_iommu; + } + if (gpu->buffer->paddr - gpu->memory_base > 0x80000000) { + ret = -EINVAL; + dev_err(gpu->dev, + "command buffer outside valid memory window\n"); + goto free_buffer; + } + + /* Setup event management */ + spin_lock_init(&gpu->event_spinlock); + init_completion(&gpu->event_free); + for (i = 0; i < ARRAY_SIZE(gpu->event); i++) { + gpu->event[i].used = false; + complete(&gpu->event_free); + } + + /* Now program the hardware */ + mutex_lock(&gpu->lock); + etnaviv_gpu_hw_init(gpu); + mutex_unlock(&gpu->lock); + + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); + + return 0; + +free_buffer: + etnaviv_gpu_cmdbuf_free(gpu->buffer); + gpu->buffer = NULL; +destroy_iommu: + etnaviv_iommu_destroy(gpu->mmu); + gpu->mmu = NULL; +fail: + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); + + return ret; +} + +#ifdef CONFIG_DEBUG_FS +struct dma_debug { + u32 address[2]; + u32 state[2]; +}; + +static void verify_dma(struct etnaviv_gpu *gpu, struct dma_debug *debug) +{ + u32 i; + + debug->address[0] = gpu_read(gpu, VIVS_FE_DMA_ADDRESS); + debug->state[0] = gpu_read(gpu, VIVS_FE_DMA_DEBUG_STATE); + + for (i = 0; i < 500; i++) { + debug->address[1] = gpu_read(gpu, VIVS_FE_DMA_ADDRESS); + debug->state[1] = gpu_read(gpu, VIVS_FE_DMA_DEBUG_STATE); + + if (debug->address[0] != debug->address[1]) + break; + + if (debug->state[0] != debug->state[1]) + break; + } +} + +int etnaviv_gpu_debugfs(struct etnaviv_gpu *gpu, struct seq_file *m) +{ + struct dma_debug debug; + u32 dma_lo, dma_hi, axi, idle; + int ret; + + seq_printf(m, "%s Status:\n", dev_name(gpu->dev)); + + ret = pm_runtime_get_sync(gpu->dev); + if (ret < 0) + return ret; + + dma_lo = gpu_read(gpu, VIVS_FE_DMA_LOW); + dma_hi = gpu_read(gpu, VIVS_FE_DMA_HIGH); + axi = gpu_read(gpu, VIVS_HI_AXI_STATUS); + idle = gpu_read(gpu, VIVS_HI_IDLE_STATE); + + verify_dma(gpu, &debug); + + seq_puts(m, "\tfeatures\n"); + seq_printf(m, "\t minor_features0: 0x%08x\n", + gpu->identity.minor_features0); + seq_printf(m, "\t minor_features1: 0x%08x\n", + gpu->identity.minor_features1); + seq_printf(m, "\t minor_features2: 0x%08x\n", + gpu->identity.minor_features2); + seq_printf(m, "\t minor_features3: 0x%08x\n", + gpu->identity.minor_features3); + seq_printf(m, "\t minor_features4: 0x%08x\n", + gpu->identity.minor_features4); + seq_printf(m, "\t minor_features5: 0x%08x\n", + gpu->identity.minor_features5); + + seq_puts(m, "\tspecs\n"); + seq_printf(m, "\t stream_count: %d\n", + gpu->identity.stream_count); + seq_printf(m, "\t register_max: %d\n", + gpu->identity.register_max); + seq_printf(m, "\t thread_count: %d\n", + gpu->identity.thread_count); + seq_printf(m, "\t vertex_cache_size: %d\n", + gpu->identity.vertex_cache_size); + seq_printf(m, "\t shader_core_count: %d\n", + gpu->identity.shader_core_count); + seq_printf(m, "\t pixel_pipes: %d\n", + gpu->identity.pixel_pipes); + seq_printf(m, "\t vertex_output_buffer_size: %d\n", + gpu->identity.vertex_output_buffer_size); + seq_printf(m, "\t buffer_size: %d\n", + gpu->identity.buffer_size); + seq_printf(m, "\t instruction_count: %d\n", + gpu->identity.instruction_count); + seq_printf(m, "\t num_constants: %d\n", + gpu->identity.num_constants); + seq_printf(m, "\t varyings_count: %d\n", + gpu->identity.varyings_count); + + seq_printf(m, "\taxi: 0x%08x\n", axi); + seq_printf(m, "\tidle: 0x%08x\n", idle); + idle |= ~gpu->idle_mask & ~VIVS_HI_IDLE_STATE_AXI_LP; + if ((idle & VIVS_HI_IDLE_STATE_FE) == 0) + seq_puts(m, "\t FE is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_DE) == 0) + seq_puts(m, "\t DE is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_PE) == 0) + seq_puts(m, "\t PE is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_SH) == 0) + seq_puts(m, "\t SH is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_PA) == 0) + seq_puts(m, "\t PA is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_SE) == 0) + seq_puts(m, "\t SE is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_RA) == 0) + seq_puts(m, "\t RA is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_TX) == 0) + seq_puts(m, "\t TX is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_VG) == 0) + seq_puts(m, "\t VG is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_IM) == 0) + seq_puts(m, "\t IM is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_FP) == 0) + seq_puts(m, "\t FP is not idle\n"); + if ((idle & VIVS_HI_IDLE_STATE_TS) == 0) + seq_puts(m, "\t TS is not idle\n"); + if (idle & VIVS_HI_IDLE_STATE_AXI_LP) + seq_puts(m, "\t AXI low power mode\n"); + + if (gpu->identity.features & chipFeatures_DEBUG_MODE) { + u32 read0 = gpu_read(gpu, VIVS_MC_DEBUG_READ0); + u32 read1 = gpu_read(gpu, VIVS_MC_DEBUG_READ1); + u32 write = gpu_read(gpu, VIVS_MC_DEBUG_WRITE); + + seq_puts(m, "\tMC\n"); + seq_printf(m, "\t read0: 0x%08x\n", read0); + seq_printf(m, "\t read1: 0x%08x\n", read1); + seq_printf(m, "\t write: 0x%08x\n", write); + } + + seq_puts(m, "\tDMA "); + + if (debug.address[0] == debug.address[1] && + debug.state[0] == debug.state[1]) { + seq_puts(m, "seems to be stuck\n"); + } else if (debug.address[0] == debug.address[1]) { + seq_puts(m, "adress is constant\n"); + } else { + seq_puts(m, "is runing\n"); + } + + seq_printf(m, "\t address 0: 0x%08x\n", debug.address[0]); + seq_printf(m, "\t address 1: 0x%08x\n", debug.address[1]); + seq_printf(m, "\t state 0: 0x%08x\n", debug.state[0]); + seq_printf(m, "\t state 1: 0x%08x\n", debug.state[1]); + seq_printf(m, "\t last fetch 64 bit word: 0x%08x 0x%08x\n", + dma_lo, dma_hi); + + ret = 0; + + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); + + return ret; +} +#endif + +/* + * Power Management: + */ +static int enable_clk(struct etnaviv_gpu *gpu) +{ + if (gpu->clk_core) + clk_prepare_enable(gpu->clk_core); + if (gpu->clk_shader) + clk_prepare_enable(gpu->clk_shader); + + return 0; +} + +static int disable_clk(struct etnaviv_gpu *gpu) +{ + if (gpu->clk_core) + clk_disable_unprepare(gpu->clk_core); + if (gpu->clk_shader) + clk_disable_unprepare(gpu->clk_shader); + + return 0; +} + +static int enable_axi(struct etnaviv_gpu *gpu) +{ + if (gpu->clk_bus) + clk_prepare_enable(gpu->clk_bus); + + return 0; +} + +static int disable_axi(struct etnaviv_gpu *gpu) +{ + if (gpu->clk_bus) + clk_disable_unprepare(gpu->clk_bus); + + return 0; +} + +/* + * Hangcheck detection for locked gpu: + */ +static void recover_worker(struct work_struct *work) +{ + struct etnaviv_gpu *gpu = container_of(work, struct etnaviv_gpu, + recover_work); + unsigned long flags; + unsigned int i; + + dev_err(gpu->dev, "hangcheck recover!\n"); + + if (pm_runtime_get_sync(gpu->dev) < 0) + return; + + mutex_lock(&gpu->lock); + + /* Only catch the first event, or when manually re-armed */ + if (etnaviv_dump_core) { + etnaviv_core_dump(gpu); + etnaviv_dump_core = false; + } + + etnaviv_hw_reset(gpu); + + /* complete all events, the GPU won't do it after the reset */ + spin_lock_irqsave(&gpu->event_spinlock, flags); + for (i = 0; i < ARRAY_SIZE(gpu->event); i++) { + if (!gpu->event[i].used) + continue; + fence_signal(gpu->event[i].fence); + gpu->event[i].fence = NULL; + gpu->event[i].used = false; + complete(&gpu->event_free); + /* + * Decrement the PM count for each stuck event. This is safe + * even in atomic context as we use ASYNC RPM here. + */ + pm_runtime_put_autosuspend(gpu->dev); + } + spin_unlock_irqrestore(&gpu->event_spinlock, flags); + gpu->completed_fence = gpu->active_fence; + + etnaviv_gpu_hw_init(gpu); + gpu->switch_context = true; + + mutex_unlock(&gpu->lock); + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); + + /* Retire the buffer objects in a work */ + etnaviv_queue_work(gpu->drm, &gpu->retire_work); +} + +static void hangcheck_timer_reset(struct etnaviv_gpu *gpu) +{ + DBG("%s", dev_name(gpu->dev)); + mod_timer(&gpu->hangcheck_timer, + round_jiffies_up(jiffies + DRM_ETNAVIV_HANGCHECK_JIFFIES)); +} + +static void hangcheck_handler(unsigned long data) +{ + struct etnaviv_gpu *gpu = (struct etnaviv_gpu *)data; + u32 fence = gpu->completed_fence; + bool progress = false; + + if (fence != gpu->hangcheck_fence) { + gpu->hangcheck_fence = fence; + progress = true; + } + + if (!progress) { + u32 dma_addr = gpu_read(gpu, VIVS_FE_DMA_ADDRESS); + int change = dma_addr - gpu->hangcheck_dma_addr; + + if (change < 0 || change > 16) { + gpu->hangcheck_dma_addr = dma_addr; + progress = true; + } + } + + if (!progress && fence_after(gpu->active_fence, fence)) { + dev_err(gpu->dev, "hangcheck detected gpu lockup!\n"); + dev_err(gpu->dev, " completed fence: %u\n", fence); + dev_err(gpu->dev, " active fence: %u\n", + gpu->active_fence); + etnaviv_queue_work(gpu->drm, &gpu->recover_work); + } + + /* if still more pending work, reset the hangcheck timer: */ + if (fence_after(gpu->active_fence, gpu->hangcheck_fence)) + hangcheck_timer_reset(gpu); +} + +static void hangcheck_disable(struct etnaviv_gpu *gpu) +{ + del_timer_sync(&gpu->hangcheck_timer); + cancel_work_sync(&gpu->recover_work); +} + +/* fence object management */ +struct etnaviv_fence { + struct etnaviv_gpu *gpu; + struct fence base; +}; + +static inline struct etnaviv_fence *to_etnaviv_fence(struct fence *fence) +{ + return container_of(fence, struct etnaviv_fence, base); +} + +static const char *etnaviv_fence_get_driver_name(struct fence *fence) +{ + return "etnaviv"; +} + +static const char *etnaviv_fence_get_timeline_name(struct fence *fence) +{ + struct etnaviv_fence *f = to_etnaviv_fence(fence); + + return dev_name(f->gpu->dev); +} + +static bool etnaviv_fence_enable_signaling(struct fence *fence) +{ + return true; +} + +static bool etnaviv_fence_signaled(struct fence *fence) +{ + struct etnaviv_fence *f = to_etnaviv_fence(fence); + + return fence_completed(f->gpu, f->base.seqno); +} + +static void etnaviv_fence_release(struct fence *fence) +{ + struct etnaviv_fence *f = to_etnaviv_fence(fence); + + kfree_rcu(f, base.rcu); +} + +static const struct fence_ops etnaviv_fence_ops = { + .get_driver_name = etnaviv_fence_get_driver_name, + .get_timeline_name = etnaviv_fence_get_timeline_name, + .enable_signaling = etnaviv_fence_enable_signaling, + .signaled = etnaviv_fence_signaled, + .wait = fence_default_wait, + .release = etnaviv_fence_release, +}; + +static struct fence *etnaviv_gpu_fence_alloc(struct etnaviv_gpu *gpu) +{ + struct etnaviv_fence *f; + + f = kzalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->gpu = gpu; + + fence_init(&f->base, &etnaviv_fence_ops, &gpu->fence_spinlock, + gpu->fence_context, ++gpu->next_fence); + + return &f->base; +} + +int etnaviv_gpu_fence_sync_obj(struct etnaviv_gem_object *etnaviv_obj, + unsigned int context, bool exclusive) +{ + struct reservation_object *robj = etnaviv_obj->resv; + struct reservation_object_list *fobj; + struct fence *fence; + int i, ret; + + if (!exclusive) { + ret = reservation_object_reserve_shared(robj); + if (ret) + return ret; + } + + /* + * If we have any shared fences, then the exclusive fence + * should be ignored as it will already have been signalled. + */ + fobj = reservation_object_get_list(robj); + if (!fobj || fobj->shared_count == 0) { + /* Wait on any existing exclusive fence which isn't our own */ + fence = reservation_object_get_excl(robj); + if (fence && fence->context != context) { + ret = fence_wait(fence, true); + if (ret) + return ret; + } + } + + if (!exclusive || !fobj) + return 0; + + for (i = 0; i < fobj->shared_count; i++) { + fence = rcu_dereference_protected(fobj->shared[i], + reservation_object_held(robj)); + if (fence->context != context) { + ret = fence_wait(fence, true); + if (ret) + return ret; + } + } + + return 0; +} + +/* + * event management: + */ + +static unsigned int event_alloc(struct etnaviv_gpu *gpu) +{ + unsigned long ret, flags; + unsigned int i, event = ~0U; + + ret = wait_for_completion_timeout(&gpu->event_free, + msecs_to_jiffies(10 * 10000)); + if (!ret) + dev_err(gpu->dev, "wait_for_completion_timeout failed"); + + spin_lock_irqsave(&gpu->event_spinlock, flags); + + /* find first free event */ + for (i = 0; i < ARRAY_SIZE(gpu->event); i++) { + if (gpu->event[i].used == false) { + gpu->event[i].used = true; + event = i; + break; + } + } + + spin_unlock_irqrestore(&gpu->event_spinlock, flags); + + return event; +} + +static void event_free(struct etnaviv_gpu *gpu, unsigned int event) +{ + unsigned long flags; + + spin_lock_irqsave(&gpu->event_spinlock, flags); + + if (gpu->event[event].used == false) { + dev_warn(gpu->dev, "event %u is already marked as free", + event); + spin_unlock_irqrestore(&gpu->event_spinlock, flags); + } else { + gpu->event[event].used = false; + spin_unlock_irqrestore(&gpu->event_spinlock, flags); + + complete(&gpu->event_free); + } +} + +/* + * Cmdstream submission/retirement: + */ + +struct etnaviv_cmdbuf *etnaviv_gpu_cmdbuf_new(struct etnaviv_gpu *gpu, u32 size, + size_t nr_bos) +{ + struct etnaviv_cmdbuf *cmdbuf; + size_t sz = size_vstruct(nr_bos, sizeof(cmdbuf->bo[0]), + sizeof(*cmdbuf)); + + cmdbuf = kzalloc(sz, GFP_KERNEL); + if (!cmdbuf) + return NULL; + + cmdbuf->vaddr = dma_alloc_writecombine(gpu->dev, size, &cmdbuf->paddr, + GFP_KERNEL); + if (!cmdbuf->vaddr) { + kfree(cmdbuf); + return NULL; + } + + cmdbuf->gpu = gpu; + cmdbuf->size = size; + + return cmdbuf; +} + +void etnaviv_gpu_cmdbuf_free(struct etnaviv_cmdbuf *cmdbuf) +{ + dma_free_writecombine(cmdbuf->gpu->dev, cmdbuf->size, + cmdbuf->vaddr, cmdbuf->paddr); + kfree(cmdbuf); +} + +static void retire_worker(struct work_struct *work) +{ + struct etnaviv_gpu *gpu = container_of(work, struct etnaviv_gpu, + retire_work); + u32 fence = gpu->completed_fence; + struct etnaviv_cmdbuf *cmdbuf, *tmp; + unsigned int i; + + mutex_lock(&gpu->lock); + list_for_each_entry_safe(cmdbuf, tmp, &gpu->active_cmd_list, node) { + if (!fence_is_signaled(cmdbuf->fence)) + break; + + list_del(&cmdbuf->node); + fence_put(cmdbuf->fence); + + for (i = 0; i < cmdbuf->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = cmdbuf->bo[i]; + + atomic_dec(&etnaviv_obj->gpu_active); + /* drop the refcount taken in etnaviv_gpu_submit */ + etnaviv_gem_put_iova(gpu, &etnaviv_obj->base); + } + + etnaviv_gpu_cmdbuf_free(cmdbuf); + } + + gpu->retired_fence = fence; + + mutex_unlock(&gpu->lock); + + wake_up_all(&gpu->fence_event); +} + +int etnaviv_gpu_wait_fence_interruptible(struct etnaviv_gpu *gpu, + u32 fence, struct timespec *timeout) +{ + int ret; + + if (fence_after(fence, gpu->next_fence)) { + DRM_ERROR("waiting on invalid fence: %u (of %u)\n", + fence, gpu->next_fence); + return -EINVAL; + } + + if (!timeout) { + /* No timeout was requested: just test for completion */ + ret = fence_completed(gpu, fence) ? 0 : -EBUSY; + } else { + unsigned long remaining = etnaviv_timeout_to_jiffies(timeout); + + ret = wait_event_interruptible_timeout(gpu->fence_event, + fence_completed(gpu, fence), + remaining); + if (ret == 0) { + DBG("timeout waiting for fence: %u (retired: %u completed: %u)", + fence, gpu->retired_fence, + gpu->completed_fence); + ret = -ETIMEDOUT; + } else if (ret != -ERESTARTSYS) { + ret = 0; + } + } + + return ret; +} + +/* + * Wait for an object to become inactive. This, on it's own, is not race + * free: the object is moved by the retire worker off the active list, and + * then the iova is put. Moreover, the object could be re-submitted just + * after we notice that it's become inactive. + * + * Although the retirement happens under the gpu lock, we don't want to hold + * that lock in this function while waiting. + */ +int etnaviv_gpu_wait_obj_inactive(struct etnaviv_gpu *gpu, + struct etnaviv_gem_object *etnaviv_obj, struct timespec *timeout) +{ + unsigned long remaining; + long ret; + + if (!timeout) + return !is_active(etnaviv_obj) ? 0 : -EBUSY; + + remaining = etnaviv_timeout_to_jiffies(timeout); + + ret = wait_event_interruptible_timeout(gpu->fence_event, + !is_active(etnaviv_obj), + remaining); + if (ret > 0) { + struct etnaviv_drm_private *priv = gpu->drm->dev_private; + + /* Synchronise with the retire worker */ + flush_workqueue(priv->wq); + return 0; + } else if (ret == -ERESTARTSYS) { + return -ERESTARTSYS; + } else { + return -ETIMEDOUT; + } +} + +int etnaviv_gpu_pm_get_sync(struct etnaviv_gpu *gpu) +{ + return pm_runtime_get_sync(gpu->dev); +} + +void etnaviv_gpu_pm_put(struct etnaviv_gpu *gpu) +{ + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); +} + +/* add bo's to gpu's ring, and kick gpu: */ +int etnaviv_gpu_submit(struct etnaviv_gpu *gpu, + struct etnaviv_gem_submit *submit, struct etnaviv_cmdbuf *cmdbuf) +{ + struct fence *fence; + unsigned int event, i; + int ret; + + ret = etnaviv_gpu_pm_get_sync(gpu); + if (ret < 0) + return ret; + + mutex_lock(&gpu->lock); + + /* + * TODO + * + * - flush + * - data endian + * - prefetch + * + */ + + event = event_alloc(gpu); + if (unlikely(event == ~0U)) { + DRM_ERROR("no free event\n"); + ret = -EBUSY; + goto out_unlock; + } + + fence = etnaviv_gpu_fence_alloc(gpu); + if (!fence) { + event_free(gpu, event); + ret = -ENOMEM; + goto out_unlock; + } + + gpu->event[event].fence = fence; + submit->fence = fence->seqno; + gpu->active_fence = submit->fence; + + if (gpu->lastctx != cmdbuf->ctx) { + gpu->mmu->need_flush = true; + gpu->switch_context = true; + gpu->lastctx = cmdbuf->ctx; + } + + etnaviv_buffer_queue(gpu, event, cmdbuf); + + cmdbuf->fence = fence; + list_add_tail(&cmdbuf->node, &gpu->active_cmd_list); + + /* We're committed to adding this command buffer, hold a PM reference */ + pm_runtime_get_noresume(gpu->dev); + + for (i = 0; i < submit->nr_bos; i++) { + struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj; + u32 iova; + + /* Each cmdbuf takes a refcount on the iova */ + etnaviv_gem_get_iova(gpu, &etnaviv_obj->base, &iova); + cmdbuf->bo[i] = etnaviv_obj; + atomic_inc(&etnaviv_obj->gpu_active); + + if (submit->bos[i].flags & ETNA_SUBMIT_BO_WRITE) + reservation_object_add_excl_fence(etnaviv_obj->resv, + fence); + else + reservation_object_add_shared_fence(etnaviv_obj->resv, + fence); + } + cmdbuf->nr_bos = submit->nr_bos; + hangcheck_timer_reset(gpu); + ret = 0; + +out_unlock: + mutex_unlock(&gpu->lock); + + etnaviv_gpu_pm_put(gpu); + + return ret; +} + +/* + * Init/Cleanup: + */ +static irqreturn_t irq_handler(int irq, void *data) +{ + struct etnaviv_gpu *gpu = data; + irqreturn_t ret = IRQ_NONE; + + u32 intr = gpu_read(gpu, VIVS_HI_INTR_ACKNOWLEDGE); + + if (intr != 0) { + int event; + + pm_runtime_mark_last_busy(gpu->dev); + + dev_dbg(gpu->dev, "intr 0x%08x\n", intr); + + if (intr & VIVS_HI_INTR_ACKNOWLEDGE_AXI_BUS_ERROR) { + dev_err(gpu->dev, "AXI bus error\n"); + intr &= ~VIVS_HI_INTR_ACKNOWLEDGE_AXI_BUS_ERROR; + } + + while ((event = ffs(intr)) != 0) { + struct fence *fence; + + event -= 1; + + intr &= ~(1 << event); + + dev_dbg(gpu->dev, "event %u\n", event); + + fence = gpu->event[event].fence; + gpu->event[event].fence = NULL; + fence_signal(fence); + + /* + * Events can be processed out of order. Eg, + * - allocate and queue event 0 + * - allocate event 1 + * - event 0 completes, we process it + * - allocate and queue event 0 + * - event 1 and event 0 complete + * we can end up processing event 0 first, then 1. + */ + if (fence_after(fence->seqno, gpu->completed_fence)) + gpu->completed_fence = fence->seqno; + + event_free(gpu, event); + + /* + * We need to balance the runtime PM count caused by + * each submission. Upon submission, we increment + * the runtime PM counter, and allocate one event. + * So here, we put the runtime PM count for each + * completed event. + */ + pm_runtime_put_autosuspend(gpu->dev); + } + + /* Retire the buffer objects in a work */ + etnaviv_queue_work(gpu->drm, &gpu->retire_work); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static int etnaviv_gpu_clk_enable(struct etnaviv_gpu *gpu) +{ + int ret; + + ret = enable_clk(gpu); + if (ret) + return ret; + + ret = enable_axi(gpu); + if (ret) { + disable_clk(gpu); + return ret; + } + + return 0; +} + +static int etnaviv_gpu_clk_disable(struct etnaviv_gpu *gpu) +{ + int ret; + + ret = disable_axi(gpu); + if (ret) + return ret; + + ret = disable_clk(gpu); + if (ret) + return ret; + + return 0; +} + +static int etnaviv_gpu_hw_suspend(struct etnaviv_gpu *gpu) +{ + if (gpu->buffer) { + unsigned long timeout; + + /* Replace the last WAIT with END */ + etnaviv_buffer_end(gpu); + + /* + * We know that only the FE is busy here, this should + * happen quickly (as the WAIT is only 200 cycles). If + * we fail, just warn and continue. + */ + timeout = jiffies + msecs_to_jiffies(100); + do { + u32 idle = gpu_read(gpu, VIVS_HI_IDLE_STATE); + + if ((idle & gpu->idle_mask) == gpu->idle_mask) + break; + + if (time_is_before_jiffies(timeout)) { + dev_warn(gpu->dev, + "timed out waiting for idle: idle=0x%x\n", + idle); + break; + } + + udelay(5); + } while (1); + } + + return etnaviv_gpu_clk_disable(gpu); +} + +#ifdef CONFIG_PM +static int etnaviv_gpu_hw_resume(struct etnaviv_gpu *gpu) +{ + u32 clock; + int ret; + + ret = mutex_lock_killable(&gpu->lock); + if (ret) + return ret; + + clock = VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS | + VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(0x40); + + etnaviv_gpu_load_clock(gpu, clock); + etnaviv_gpu_hw_init(gpu); + + gpu->switch_context = true; + + mutex_unlock(&gpu->lock); + + return 0; +} +#endif + +static int etnaviv_gpu_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct etnaviv_drm_private *priv = drm->dev_private; + struct etnaviv_gpu *gpu = dev_get_drvdata(dev); + int ret; + +#ifdef CONFIG_PM + ret = pm_runtime_get_sync(gpu->dev); +#else + ret = etnaviv_gpu_clk_enable(gpu); +#endif + if (ret < 0) + return ret; + + gpu->drm = drm; + gpu->fence_context = fence_context_alloc(1); + spin_lock_init(&gpu->fence_spinlock); + + INIT_LIST_HEAD(&gpu->active_cmd_list); + INIT_WORK(&gpu->retire_work, retire_worker); + INIT_WORK(&gpu->recover_work, recover_worker); + init_waitqueue_head(&gpu->fence_event); + + setup_timer(&gpu->hangcheck_timer, hangcheck_handler, + (unsigned long)gpu); + + priv->gpu[priv->num_gpus++] = gpu; + + pm_runtime_mark_last_busy(gpu->dev); + pm_runtime_put_autosuspend(gpu->dev); + + return 0; +} + +static void etnaviv_gpu_unbind(struct device *dev, struct device *master, + void *data) +{ + struct etnaviv_gpu *gpu = dev_get_drvdata(dev); + + DBG("%s", dev_name(gpu->dev)); + + hangcheck_disable(gpu); + +#ifdef CONFIG_PM + pm_runtime_get_sync(gpu->dev); + pm_runtime_put_sync_suspend(gpu->dev); +#else + etnaviv_gpu_hw_suspend(gpu); +#endif + + if (gpu->buffer) { + etnaviv_gpu_cmdbuf_free(gpu->buffer); + gpu->buffer = NULL; + } + + if (gpu->mmu) { + etnaviv_iommu_destroy(gpu->mmu); + gpu->mmu = NULL; + } + + gpu->drm = NULL; +} + +static const struct component_ops gpu_ops = { + .bind = etnaviv_gpu_bind, + .unbind = etnaviv_gpu_unbind, +}; + +static const struct of_device_id etnaviv_gpu_match[] = { + { + .compatible = "vivante,gc" + }, + { /* sentinel */ } +}; + +static int etnaviv_gpu_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct etnaviv_gpu *gpu; + int err = 0; + + gpu = devm_kzalloc(dev, sizeof(*gpu), GFP_KERNEL); + if (!gpu) + return -ENOMEM; + + gpu->dev = &pdev->dev; + mutex_init(&gpu->lock); + + /* + * Set the GPU base address to the start of physical memory. This + * ensures that if we have up to 2GB, the v1 MMU can address the + * highest memory. This is important as command buffers may be + * allocated outside of this limit. + */ + gpu->memory_base = PHYS_OFFSET; + + /* Map registers: */ + gpu->mmio = etnaviv_ioremap(pdev, NULL, dev_name(gpu->dev)); + if (IS_ERR(gpu->mmio)) + return PTR_ERR(gpu->mmio); + + /* Get Interrupt: */ + gpu->irq = platform_get_irq(pdev, 0); + if (gpu->irq < 0) { + err = gpu->irq; + dev_err(dev, "failed to get irq: %d\n", err); + goto fail; + } + + err = devm_request_irq(&pdev->dev, gpu->irq, irq_handler, 0, + dev_name(gpu->dev), gpu); + if (err) { + dev_err(dev, "failed to request IRQ%u: %d\n", gpu->irq, err); + goto fail; + } + + /* Get Clocks: */ + gpu->clk_bus = devm_clk_get(&pdev->dev, "bus"); + DBG("clk_bus: %p", gpu->clk_bus); + if (IS_ERR(gpu->clk_bus)) + gpu->clk_bus = NULL; + + gpu->clk_core = devm_clk_get(&pdev->dev, "core"); + DBG("clk_core: %p", gpu->clk_core); + if (IS_ERR(gpu->clk_core)) + gpu->clk_core = NULL; + + gpu->clk_shader = devm_clk_get(&pdev->dev, "shader"); + DBG("clk_shader: %p", gpu->clk_shader); + if (IS_ERR(gpu->clk_shader)) + gpu->clk_shader = NULL; + + /* TODO: figure out max mapped size */ + dev_set_drvdata(dev, gpu); + + /* + * We treat the device as initially suspended. The runtime PM + * autosuspend delay is rather arbitary: no measurements have + * yet been performed to determine an appropriate value. + */ + pm_runtime_use_autosuspend(gpu->dev); + pm_runtime_set_autosuspend_delay(gpu->dev, 200); + pm_runtime_enable(gpu->dev); + + err = component_add(&pdev->dev, &gpu_ops); + if (err < 0) { + dev_err(&pdev->dev, "failed to register component: %d\n", err); + goto fail; + } + + return 0; + +fail: + return err; +} + +static int etnaviv_gpu_platform_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &gpu_ops); + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_PM +static int etnaviv_gpu_rpm_suspend(struct device *dev) +{ + struct etnaviv_gpu *gpu = dev_get_drvdata(dev); + u32 idle, mask; + + /* If we have outstanding fences, we're not idle */ + if (gpu->completed_fence != gpu->active_fence) + return -EBUSY; + + /* Check whether the hardware (except FE) is idle */ + mask = gpu->idle_mask & ~VIVS_HI_IDLE_STATE_FE; + idle = gpu_read(gpu, VIVS_HI_IDLE_STATE) & mask; + if (idle != mask) + return -EBUSY; + + return etnaviv_gpu_hw_suspend(gpu); +} + +static int etnaviv_gpu_rpm_resume(struct device *dev) +{ + struct etnaviv_gpu *gpu = dev_get_drvdata(dev); + int ret; + + ret = etnaviv_gpu_clk_enable(gpu); + if (ret) + return ret; + + /* Re-initialise the basic hardware state */ + if (gpu->drm && gpu->buffer) { + ret = etnaviv_gpu_hw_resume(gpu); + if (ret) { + etnaviv_gpu_clk_disable(gpu); + return ret; + } + } + + return 0; +} +#endif + +static const struct dev_pm_ops etnaviv_gpu_pm_ops = { + SET_RUNTIME_PM_OPS(etnaviv_gpu_rpm_suspend, etnaviv_gpu_rpm_resume, + NULL) +}; + +struct platform_driver etnaviv_gpu_driver = { + .driver = { + .name = "etnaviv-gpu", + .owner = THIS_MODULE, + .pm = &etnaviv_gpu_pm_ops, + .of_match_table = etnaviv_gpu_match, + }, + .probe = etnaviv_gpu_platform_probe, + .remove = etnaviv_gpu_platform_remove, + .id_table = gpu_ids, +}; diff --git b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h new file mode 100644 index 0000000..f233ac4 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_gpu.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_GPU_H__ +#define __ETNAVIV_GPU_H__ + +#include +#include + +#include "etnaviv_drv.h" + +struct etnaviv_gem_submit; + +struct etnaviv_chip_identity { + /* Chip model. */ + u32 model; + + /* Revision value.*/ + u32 revision; + + /* Supported feature fields. */ + u32 features; + + /* Supported minor feature fields. */ + u32 minor_features0; + + /* Supported minor feature 1 fields. */ + u32 minor_features1; + + /* Supported minor feature 2 fields. */ + u32 minor_features2; + + /* Supported minor feature 3 fields. */ + u32 minor_features3; + + /* Supported minor feature 4 fields. */ + u32 minor_features4; + + /* Supported minor feature 5 fields. */ + u32 minor_features5; + + /* Number of streams supported. */ + u32 stream_count; + + /* Total number of temporary registers per thread. */ + u32 register_max; + + /* Maximum number of threads. */ + u32 thread_count; + + /* Number of shader cores. */ + u32 shader_core_count; + + /* Size of the vertex cache. */ + u32 vertex_cache_size; + + /* Number of entries in the vertex output buffer. */ + u32 vertex_output_buffer_size; + + /* Number of pixel pipes. */ + u32 pixel_pipes; + + /* Number of instructions. */ + u32 instruction_count; + + /* Number of constants. */ + u32 num_constants; + + /* Buffer size */ + u32 buffer_size; + + /* Number of varyings */ + u8 varyings_count; +}; + +struct etnaviv_event { + bool used; + struct fence *fence; +}; + +struct etnaviv_cmdbuf; + +struct etnaviv_gpu { + struct drm_device *drm; + struct device *dev; + struct mutex lock; + struct etnaviv_chip_identity identity; + struct etnaviv_file_private *lastctx; + bool switch_context; + + /* 'ring'-buffer: */ + struct etnaviv_cmdbuf *buffer; + + /* bus base address of memory */ + u32 memory_base; + + /* event management: */ + struct etnaviv_event event[30]; + struct completion event_free; + spinlock_t event_spinlock; + + /* list of currently in-flight command buffers */ + struct list_head active_cmd_list; + + u32 idle_mask; + + /* Fencing support */ + u32 next_fence; + u32 active_fence; + u32 completed_fence; + u32 retired_fence; + wait_queue_head_t fence_event; + unsigned int fence_context; + spinlock_t fence_spinlock; + + /* worker for handling active-list retiring: */ + struct work_struct retire_work; + + void __iomem *mmio; + int irq; + + struct etnaviv_iommu *mmu; + + /* Power Control: */ + struct clk *clk_bus; + struct clk *clk_core; + struct clk *clk_shader; + + /* Hang Detction: */ +#define DRM_ETNAVIV_HANGCHECK_PERIOD 500 /* in ms */ +#define DRM_ETNAVIV_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_ETNAVIV_HANGCHECK_PERIOD) + struct timer_list hangcheck_timer; + u32 hangcheck_fence; + u32 hangcheck_dma_addr; + struct work_struct recover_work; +}; + +struct etnaviv_cmdbuf { + /* device this cmdbuf is allocated for */ + struct etnaviv_gpu *gpu; + /* user context key, must be unique between all active users */ + struct etnaviv_file_private *ctx; + /* cmdbuf properties */ + void *vaddr; + dma_addr_t paddr; + u32 size; + u32 user_size; + /* fence after which this buffer is to be disposed */ + struct fence *fence; + /* target exec state */ + u32 exec_state; + /* per GPU in-flight list */ + struct list_head node; + /* BOs attached to this command buffer */ + unsigned int nr_bos; + struct etnaviv_gem_object *bo[0]; +}; + +static inline void gpu_write(struct etnaviv_gpu *gpu, u32 reg, u32 data) +{ + etnaviv_writel(data, gpu->mmio + reg); +} + +static inline u32 gpu_read(struct etnaviv_gpu *gpu, u32 reg) +{ + return etnaviv_readl(gpu->mmio + reg); +} + +static inline bool fence_completed(struct etnaviv_gpu *gpu, u32 fence) +{ + return fence_after_eq(gpu->completed_fence, fence); +} + +static inline bool fence_retired(struct etnaviv_gpu *gpu, u32 fence) +{ + return fence_after_eq(gpu->retired_fence, fence); +} + +int etnaviv_gpu_get_param(struct etnaviv_gpu *gpu, u32 param, u64 *value); + +int etnaviv_gpu_init(struct etnaviv_gpu *gpu); + +#ifdef CONFIG_DEBUG_FS +int etnaviv_gpu_debugfs(struct etnaviv_gpu *gpu, struct seq_file *m); +#endif + +int etnaviv_gpu_fence_sync_obj(struct etnaviv_gem_object *etnaviv_obj, + unsigned int context, bool exclusive); + +void etnaviv_gpu_retire(struct etnaviv_gpu *gpu); +int etnaviv_gpu_wait_fence_interruptible(struct etnaviv_gpu *gpu, + u32 fence, struct timespec *timeout); +int etnaviv_gpu_wait_obj_inactive(struct etnaviv_gpu *gpu, + struct etnaviv_gem_object *etnaviv_obj, struct timespec *timeout); +int etnaviv_gpu_submit(struct etnaviv_gpu *gpu, + struct etnaviv_gem_submit *submit, struct etnaviv_cmdbuf *cmdbuf); +struct etnaviv_cmdbuf *etnaviv_gpu_cmdbuf_new(struct etnaviv_gpu *gpu, + u32 size, size_t nr_bos); +void etnaviv_gpu_cmdbuf_free(struct etnaviv_cmdbuf *cmdbuf); +int etnaviv_gpu_pm_get_sync(struct etnaviv_gpu *gpu); +void etnaviv_gpu_pm_put(struct etnaviv_gpu *gpu); + +extern struct platform_driver etnaviv_gpu_driver; + +#endif /* __ETNAVIV_GPU_H__ */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_iommu.c b/drivers/gpu/drm/etnaviv/etnaviv_iommu.c new file mode 100644 index 0000000..522cfd4 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_iommu.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2014 Christian Gmeiner + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "etnaviv_gpu.h" +#include "etnaviv_mmu.h" +#include "etnaviv_iommu.h" +#include "state_hi.xml.h" + +#define PT_SIZE SZ_2M +#define PT_ENTRIES (PT_SIZE / sizeof(u32)) + +#define GPU_MEM_START 0x80000000 + +struct etnaviv_iommu_domain_pgtable { + u32 *pgtable; + dma_addr_t paddr; +}; + +struct etnaviv_iommu_domain { + struct iommu_domain domain; + struct device *dev; + void *bad_page_cpu; + dma_addr_t bad_page_dma; + struct etnaviv_iommu_domain_pgtable pgtable; + spinlock_t map_lock; +}; + +static struct etnaviv_iommu_domain *to_etnaviv_domain(struct iommu_domain *domain) +{ + return container_of(domain, struct etnaviv_iommu_domain, domain); +} + +static int pgtable_alloc(struct etnaviv_iommu_domain_pgtable *pgtable, + size_t size) +{ + pgtable->pgtable = dma_alloc_coherent(NULL, size, &pgtable->paddr, GFP_KERNEL); + if (!pgtable->pgtable) + return -ENOMEM; + + return 0; +} + +static void pgtable_free(struct etnaviv_iommu_domain_pgtable *pgtable, + size_t size) +{ + dma_free_coherent(NULL, size, pgtable->pgtable, pgtable->paddr); +} + +static u32 pgtable_read(struct etnaviv_iommu_domain_pgtable *pgtable, + unsigned long iova) +{ + /* calcuate index into page table */ + unsigned int index = (iova - GPU_MEM_START) / SZ_4K; + phys_addr_t paddr; + + paddr = pgtable->pgtable[index]; + + return paddr; +} + +static void pgtable_write(struct etnaviv_iommu_domain_pgtable *pgtable, + unsigned long iova, phys_addr_t paddr) +{ + /* calcuate index into page table */ + unsigned int index = (iova - GPU_MEM_START) / SZ_4K; + + pgtable->pgtable[index] = paddr; +} + +static int __etnaviv_iommu_init(struct etnaviv_iommu_domain *etnaviv_domain) +{ + u32 *p; + int ret, i; + + etnaviv_domain->bad_page_cpu = dma_alloc_coherent(etnaviv_domain->dev, + SZ_4K, + &etnaviv_domain->bad_page_dma, + GFP_KERNEL); + if (!etnaviv_domain->bad_page_cpu) + return -ENOMEM; + + p = etnaviv_domain->bad_page_cpu; + for (i = 0; i < SZ_4K / 4; i++) + *p++ = 0xdead55aa; + + ret = pgtable_alloc(&etnaviv_domain->pgtable, PT_SIZE); + if (ret < 0) { + dma_free_coherent(etnaviv_domain->dev, SZ_4K, + etnaviv_domain->bad_page_cpu, + etnaviv_domain->bad_page_dma); + return ret; + } + + for (i = 0; i < PT_ENTRIES; i++) + etnaviv_domain->pgtable.pgtable[i] = + etnaviv_domain->bad_page_dma; + + spin_lock_init(&etnaviv_domain->map_lock); + + return 0; +} + +static void etnaviv_domain_free(struct iommu_domain *domain) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + + pgtable_free(&etnaviv_domain->pgtable, PT_SIZE); + + dma_free_coherent(etnaviv_domain->dev, SZ_4K, + etnaviv_domain->bad_page_cpu, + etnaviv_domain->bad_page_dma); + + kfree(etnaviv_domain); +} + +static int etnaviv_iommuv1_map(struct iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t size, int prot) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + + if (size != SZ_4K) + return -EINVAL; + + spin_lock(&etnaviv_domain->map_lock); + pgtable_write(&etnaviv_domain->pgtable, iova, paddr); + spin_unlock(&etnaviv_domain->map_lock); + + return 0; +} + +static size_t etnaviv_iommuv1_unmap(struct iommu_domain *domain, + unsigned long iova, size_t size) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + + if (size != SZ_4K) + return -EINVAL; + + spin_lock(&etnaviv_domain->map_lock); + pgtable_write(&etnaviv_domain->pgtable, iova, + etnaviv_domain->bad_page_dma); + spin_unlock(&etnaviv_domain->map_lock); + + return SZ_4K; +} + +static phys_addr_t etnaviv_iommu_iova_to_phys(struct iommu_domain *domain, + dma_addr_t iova) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + + return pgtable_read(&etnaviv_domain->pgtable, iova); +} + +static size_t etnaviv_iommuv1_dump_size(struct iommu_domain *domain) +{ + return PT_SIZE; +} + +static void etnaviv_iommuv1_dump(struct iommu_domain *domain, void *buf) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + + memcpy(buf, etnaviv_domain->pgtable.pgtable, PT_SIZE); +} + +static struct etnaviv_iommu_ops etnaviv_iommu_ops = { + .ops = { + .domain_free = etnaviv_domain_free, + .map = etnaviv_iommuv1_map, + .unmap = etnaviv_iommuv1_unmap, + .iova_to_phys = etnaviv_iommu_iova_to_phys, + .pgsize_bitmap = SZ_4K, + }, + .dump_size = etnaviv_iommuv1_dump_size, + .dump = etnaviv_iommuv1_dump, +}; + +void etnaviv_iommu_domain_restore(struct etnaviv_gpu *gpu, + struct iommu_domain *domain) +{ + struct etnaviv_iommu_domain *etnaviv_domain = to_etnaviv_domain(domain); + u32 pgtable; + + /* set page table address in MC */ + pgtable = (u32)etnaviv_domain->pgtable.paddr; + + gpu_write(gpu, VIVS_MC_MMU_FE_PAGE_TABLE, pgtable); + gpu_write(gpu, VIVS_MC_MMU_TX_PAGE_TABLE, pgtable); + gpu_write(gpu, VIVS_MC_MMU_PE_PAGE_TABLE, pgtable); + gpu_write(gpu, VIVS_MC_MMU_PEZ_PAGE_TABLE, pgtable); + gpu_write(gpu, VIVS_MC_MMU_RA_PAGE_TABLE, pgtable); +} + +struct iommu_domain *etnaviv_iommu_domain_alloc(struct etnaviv_gpu *gpu) +{ + struct etnaviv_iommu_domain *etnaviv_domain; + int ret; + + etnaviv_domain = kzalloc(sizeof(*etnaviv_domain), GFP_KERNEL); + if (!etnaviv_domain) + return NULL; + + etnaviv_domain->dev = gpu->dev; + + etnaviv_domain->domain.type = __IOMMU_DOMAIN_PAGING; + etnaviv_domain->domain.ops = &etnaviv_iommu_ops.ops; + etnaviv_domain->domain.geometry.aperture_start = GPU_MEM_START; + etnaviv_domain->domain.geometry.aperture_end = GPU_MEM_START + PT_ENTRIES * SZ_4K - 1; + + ret = __etnaviv_iommu_init(etnaviv_domain); + if (ret) + goto out_free; + + return &etnaviv_domain->domain; + +out_free: + kfree(etnaviv_domain); + return NULL; +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_iommu.h b/drivers/gpu/drm/etnaviv/etnaviv_iommu.h new file mode 100644 index 0000000..cf45503 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_iommu.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 Christian Gmeiner + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_IOMMU_H__ +#define __ETNAVIV_IOMMU_H__ + +#include +struct etnaviv_gpu; + +struct iommu_domain *etnaviv_iommu_domain_alloc(struct etnaviv_gpu *gpu); +void etnaviv_iommu_domain_restore(struct etnaviv_gpu *gpu, + struct iommu_domain *domain); +struct iommu_domain *etnaviv_iommu_v2_domain_alloc(struct etnaviv_gpu *gpu); + +#endif /* __ETNAVIV_IOMMU_H__ */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.c b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.c new file mode 100644 index 0000000..fbb4aed --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.c @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 Christian Gmeiner + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "etnaviv_gpu.h" +#include "etnaviv_iommu.h" +#include "state_hi.xml.h" + + +struct iommu_domain *etnaviv_iommu_v2_domain_alloc(struct etnaviv_gpu *gpu) +{ + /* TODO */ + return NULL; +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.h b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.h new file mode 100644 index 0000000..603ea41 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_iommu_v2.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 Christian Gmeiner + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_IOMMU_V2_H__ +#define __ETNAVIV_IOMMU_V2_H__ + +#include +struct etnaviv_gpu; + +struct iommu_domain *etnaviv_iommu_v2_domain_alloc(struct etnaviv_gpu *gpu); + +#endif /* __ETNAVIV_IOMMU_V2_H__ */ diff --git b/drivers/gpu/drm/etnaviv/etnaviv_mmu.c b/drivers/gpu/drm/etnaviv/etnaviv_mmu.c new file mode 100644 index 0000000..6743bc6 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_mmu.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "etnaviv_drv.h" +#include "etnaviv_gem.h" +#include "etnaviv_gpu.h" +#include "etnaviv_mmu.h" + +static int etnaviv_fault_handler(struct iommu_domain *iommu, struct device *dev, + unsigned long iova, int flags, void *arg) +{ + DBG("*** fault: iova=%08lx, flags=%d", iova, flags); + return 0; +} + +int etnaviv_iommu_map(struct etnaviv_iommu *iommu, u32 iova, + struct sg_table *sgt, unsigned len, int prot) +{ + struct iommu_domain *domain = iommu->domain; + struct scatterlist *sg; + unsigned int da = iova; + unsigned int i, j; + int ret; + + if (!domain || !sgt) + return -EINVAL; + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + u32 pa = sg_dma_address(sg) - sg->offset; + size_t bytes = sg_dma_len(sg) + sg->offset; + + VERB("map[%d]: %08x %08x(%zx)", i, iova, pa, bytes); + + ret = iommu_map(domain, da, pa, bytes, prot); + if (ret) + goto fail; + + da += bytes; + } + + return 0; + +fail: + da = iova; + + for_each_sg(sgt->sgl, sg, i, j) { + size_t bytes = sg_dma_len(sg) + sg->offset; + + iommu_unmap(domain, da, bytes); + da += bytes; + } + return ret; +} + +int etnaviv_iommu_unmap(struct etnaviv_iommu *iommu, u32 iova, + struct sg_table *sgt, unsigned len) +{ + struct iommu_domain *domain = iommu->domain; + struct scatterlist *sg; + unsigned int da = iova; + int i; + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + size_t bytes = sg_dma_len(sg) + sg->offset; + size_t unmapped; + + unmapped = iommu_unmap(domain, da, bytes); + if (unmapped < bytes) + return unmapped; + + VERB("unmap[%d]: %08x(%zx)", i, iova, bytes); + + BUG_ON(!PAGE_ALIGNED(bytes)); + + da += bytes; + } + + return 0; +} + +static void etnaviv_iommu_remove_mapping(struct etnaviv_iommu *mmu, + struct etnaviv_vram_mapping *mapping) +{ + struct etnaviv_gem_object *etnaviv_obj = mapping->object; + + etnaviv_iommu_unmap(mmu, mapping->vram_node.start, + etnaviv_obj->sgt, etnaviv_obj->base.size); + drm_mm_remove_node(&mapping->vram_node); +} + +int etnaviv_iommu_map_gem(struct etnaviv_iommu *mmu, + struct etnaviv_gem_object *etnaviv_obj, u32 memory_base, + struct etnaviv_vram_mapping *mapping) +{ + struct etnaviv_vram_mapping *free = NULL; + struct sg_table *sgt = etnaviv_obj->sgt; + struct drm_mm_node *node; + int ret; + + lockdep_assert_held(&etnaviv_obj->lock); + + mutex_lock(&mmu->lock); + + /* v1 MMU can optimize single entry (contiguous) scatterlists */ + if (sgt->nents == 1 && !(etnaviv_obj->flags & ETNA_BO_FORCE_MMU)) { + u32 iova; + + iova = sg_dma_address(sgt->sgl) - memory_base; + if (iova < 0x80000000 - sg_dma_len(sgt->sgl)) { + mapping->iova = iova; + list_add_tail(&mapping->mmu_node, &mmu->mappings); + mutex_unlock(&mmu->lock); + return 0; + } + } + + node = &mapping->vram_node; + while (1) { + struct etnaviv_vram_mapping *m, *n; + struct list_head list; + bool found; + + ret = drm_mm_insert_node_in_range(&mmu->mm, node, + etnaviv_obj->base.size, 0, mmu->last_iova, ~0UL, + DRM_MM_SEARCH_DEFAULT); + + if (ret != -ENOSPC) + break; + + /* + * If we did not search from the start of the MMU region, + * try again in case there are free slots. + */ + if (mmu->last_iova) { + mmu->last_iova = 0; + mmu->need_flush = true; + continue; + } + + /* Try to retire some entries */ + drm_mm_init_scan(&mmu->mm, etnaviv_obj->base.size, 0, 0); + + found = 0; + INIT_LIST_HEAD(&list); + list_for_each_entry(free, &mmu->mappings, mmu_node) { + /* If this vram node has not been used, skip this. */ + if (!free->vram_node.mm) + continue; + + /* + * If the iova is pinned, then it's in-use, + * so we must keep its mapping. + */ + if (free->use) + continue; + + list_add(&free->scan_node, &list); + if (drm_mm_scan_add_block(&free->vram_node)) { + found = true; + break; + } + } + + if (!found) { + /* Nothing found, clean up and fail */ + list_for_each_entry_safe(m, n, &list, scan_node) + BUG_ON(drm_mm_scan_remove_block(&m->vram_node)); + break; + } + + /* + * drm_mm does not allow any other operations while + * scanning, so we have to remove all blocks first. + * If drm_mm_scan_remove_block() returns false, we + * can leave the block pinned. + */ + list_for_each_entry_safe(m, n, &list, scan_node) + if (!drm_mm_scan_remove_block(&m->vram_node)) + list_del_init(&m->scan_node); + + /* + * Unmap the blocks which need to be reaped from the MMU. + * Clear the mmu pointer to prevent the get_iova finding + * this mapping. + */ + list_for_each_entry_safe(m, n, &list, scan_node) { + etnaviv_iommu_remove_mapping(mmu, m); + m->mmu = NULL; + list_del_init(&m->mmu_node); + list_del_init(&m->scan_node); + } + + /* + * We removed enough mappings so that the new allocation will + * succeed. Ensure that the MMU will be flushed before the + * associated commit requesting this mapping, and retry the + * allocation one more time. + */ + mmu->need_flush = true; + } + + if (ret < 0) { + mutex_unlock(&mmu->lock); + return ret; + } + + mmu->last_iova = node->start + etnaviv_obj->base.size; + mapping->iova = node->start; + ret = etnaviv_iommu_map(mmu, node->start, sgt, etnaviv_obj->base.size, + IOMMU_READ | IOMMU_WRITE); + + if (ret < 0) { + drm_mm_remove_node(node); + mutex_unlock(&mmu->lock); + return ret; + } + + list_add_tail(&mapping->mmu_node, &mmu->mappings); + mutex_unlock(&mmu->lock); + + return ret; +} + +void etnaviv_iommu_unmap_gem(struct etnaviv_iommu *mmu, + struct etnaviv_vram_mapping *mapping) +{ + WARN_ON(mapping->use); + + mutex_lock(&mmu->lock); + + /* If the vram node is on the mm, unmap and remove the node */ + if (mapping->vram_node.mm == &mmu->mm) + etnaviv_iommu_remove_mapping(mmu, mapping); + + list_del(&mapping->mmu_node); + mutex_unlock(&mmu->lock); +} + +void etnaviv_iommu_destroy(struct etnaviv_iommu *mmu) +{ + drm_mm_takedown(&mmu->mm); + iommu_domain_free(mmu->domain); + kfree(mmu); +} + +struct etnaviv_iommu *etnaviv_iommu_new(struct etnaviv_gpu *gpu, + struct iommu_domain *domain, enum etnaviv_iommu_version version) +{ + struct etnaviv_iommu *mmu; + + mmu = kzalloc(sizeof(*mmu), GFP_KERNEL); + if (!mmu) + return ERR_PTR(-ENOMEM); + + mmu->domain = domain; + mmu->gpu = gpu; + mmu->version = version; + mutex_init(&mmu->lock); + INIT_LIST_HEAD(&mmu->mappings); + + drm_mm_init(&mmu->mm, domain->geometry.aperture_start, + domain->geometry.aperture_end - + domain->geometry.aperture_start + 1); + + iommu_set_fault_handler(domain, etnaviv_fault_handler, gpu->dev); + + return mmu; +} + +size_t etnaviv_iommu_dump_size(struct etnaviv_iommu *iommu) +{ + struct etnaviv_iommu_ops *ops; + + ops = container_of(iommu->domain->ops, struct etnaviv_iommu_ops, ops); + + return ops->dump_size(iommu->domain); +} + +void etnaviv_iommu_dump(struct etnaviv_iommu *iommu, void *buf) +{ + struct etnaviv_iommu_ops *ops; + + ops = container_of(iommu->domain->ops, struct etnaviv_iommu_ops, ops); + + ops->dump(iommu->domain, buf); +} diff --git b/drivers/gpu/drm/etnaviv/etnaviv_mmu.h b/drivers/gpu/drm/etnaviv/etnaviv_mmu.h new file mode 100644 index 0000000..fff215a --- /dev/null +++ b/drivers/gpu/drm/etnaviv/etnaviv_mmu.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 Etnaviv Project + * + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __ETNAVIV_MMU_H__ +#define __ETNAVIV_MMU_H__ + +#include + +enum etnaviv_iommu_version { + ETNAVIV_IOMMU_V1 = 0, + ETNAVIV_IOMMU_V2, +}; + +struct etnaviv_gpu; +struct etnaviv_vram_mapping; + +struct etnaviv_iommu_ops { + struct iommu_ops ops; + size_t (*dump_size)(struct iommu_domain *); + void (*dump)(struct iommu_domain *, void *); +}; + +struct etnaviv_iommu { + struct etnaviv_gpu *gpu; + struct iommu_domain *domain; + + enum etnaviv_iommu_version version; + + /* memory manager for GPU address area */ + struct mutex lock; + struct list_head mappings; + struct drm_mm mm; + u32 last_iova; + bool need_flush; +}; + +struct etnaviv_gem_object; + +int etnaviv_iommu_attach(struct etnaviv_iommu *iommu, const char **names, + int cnt); +int etnaviv_iommu_map(struct etnaviv_iommu *iommu, u32 iova, + struct sg_table *sgt, unsigned len, int prot); +int etnaviv_iommu_unmap(struct etnaviv_iommu *iommu, u32 iova, + struct sg_table *sgt, unsigned len); +int etnaviv_iommu_map_gem(struct etnaviv_iommu *mmu, + struct etnaviv_gem_object *etnaviv_obj, u32 memory_base, + struct etnaviv_vram_mapping *mapping); +void etnaviv_iommu_unmap_gem(struct etnaviv_iommu *mmu, + struct etnaviv_vram_mapping *mapping); +void etnaviv_iommu_destroy(struct etnaviv_iommu *iommu); + +size_t etnaviv_iommu_dump_size(struct etnaviv_iommu *iommu); +void etnaviv_iommu_dump(struct etnaviv_iommu *iommu, void *buf); + +struct etnaviv_iommu *etnaviv_iommu_new(struct etnaviv_gpu *gpu, + struct iommu_domain *domain, enum etnaviv_iommu_version version); + +#endif /* __ETNAVIV_MMU_H__ */ diff --git b/drivers/gpu/drm/etnaviv/state.xml.h b/drivers/gpu/drm/etnaviv/state.xml.h new file mode 100644 index 0000000..3682183 --- /dev/null +++ b/drivers/gpu/drm/etnaviv/state.xml.h @@ -0,0 +1,351 @@ +#ifndef STATE_XML +#define STATE_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://0x04.net/cgit/index.cgi/rules-ng-ng +git clone git://0x04.net/rules-ng-ng + +The rules-ng-ng source files this header was generated from are: +- state.xml ( 18882 bytes, from 2015-03-25 11:42:32) +- common.xml ( 18437 bytes, from 2015-03-25 11:27:41) +- state_hi.xml ( 23420 bytes, from 2015-03-25 11:47:21) +- state_2d.xml ( 51549 bytes, from 2015-03-25 11:25:06) +- state_3d.xml ( 54600 bytes, from 2015-03-25 11:25:19) +- state_vg.xml ( 5973 bytes, from 2015-03-25 11:26:01) + +Copyright (C) 2015 +*/ + + +#define VARYING_COMPONENT_USE_UNUSED 0x00000000 +#define VARYING_COMPONENT_USE_USED 0x00000001 +#define VARYING_COMPONENT_USE_POINTCOORD_X 0x00000002 +#define VARYING_COMPONENT_USE_POINTCOORD_Y 0x00000003 +#define FE_VERTEX_STREAM_CONTROL_VERTEX_STRIDE__MASK 0x000000ff +#define FE_VERTEX_STREAM_CONTROL_VERTEX_STRIDE__SHIFT 0 +#define FE_VERTEX_STREAM_CONTROL_VERTEX_STRIDE(x) (((x) << FE_VERTEX_STREAM_CONTROL_VERTEX_STRIDE__SHIFT) & FE_VERTEX_STREAM_CONTROL_VERTEX_STRIDE__MASK) +#define VIVS_FE 0x00000000 + +#define VIVS_FE_VERTEX_ELEMENT_CONFIG(i0) (0x00000600 + 0x4*(i0)) +#define VIVS_FE_VERTEX_ELEMENT_CONFIG__ESIZE 0x00000004 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG__LEN 0x00000010 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE__MASK 0x0000000f +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE__SHIFT 0 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_BYTE 0x00000000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_UNSIGNED_BYTE 0x00000001 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_SHORT 0x00000002 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_UNSIGNED_SHORT 0x00000003 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_INT 0x00000004 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_UNSIGNED_INT 0x00000005 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_FLOAT 0x00000008 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_HALF_FLOAT 0x00000009 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_FIXED 0x0000000b +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_INT_10_10_10_2 0x0000000c +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_TYPE_UNSIGNED_INT_10_10_10_2 0x0000000d +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_ENDIAN__MASK 0x00000030 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_ENDIAN__SHIFT 4 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_ENDIAN(x) (((x) << VIVS_FE_VERTEX_ELEMENT_CONFIG_ENDIAN__SHIFT) & VIVS_FE_VERTEX_ELEMENT_CONFIG_ENDIAN__MASK) +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NONCONSECUTIVE 0x00000080 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_STREAM__MASK 0x00000700 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_STREAM__SHIFT 8 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_STREAM(x) (((x) << VIVS_FE_VERTEX_ELEMENT_CONFIG_STREAM__SHIFT) & VIVS_FE_VERTEX_ELEMENT_CONFIG_STREAM__MASK) +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NUM__MASK 0x00003000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NUM__SHIFT 12 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NUM(x) (((x) << VIVS_FE_VERTEX_ELEMENT_CONFIG_NUM__SHIFT) & VIVS_FE_VERTEX_ELEMENT_CONFIG_NUM__MASK) +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NORMALIZE__MASK 0x0000c000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NORMALIZE__SHIFT 14 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NORMALIZE_OFF 0x00000000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_NORMALIZE_ON 0x00008000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_START__MASK 0x00ff0000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_START__SHIFT 16 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_START(x) (((x) << VIVS_FE_VERTEX_ELEMENT_CONFIG_START__SHIFT) & VIVS_FE_VERTEX_ELEMENT_CONFIG_START__MASK) +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_END__MASK 0xff000000 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_END__SHIFT 24 +#define VIVS_FE_VERTEX_ELEMENT_CONFIG_END(x) (((x) << VIVS_FE_VERTEX_ELEMENT_CONFIG_END__SHIFT) & VIVS_FE_VERTEX_ELEMENT_CONFIG_END__MASK) + +#define VIVS_FE_CMD_STREAM_BASE_ADDR 0x00000640 + +#define VIVS_FE_INDEX_STREAM_BASE_ADDR 0x00000644 + +#define VIVS_FE_INDEX_STREAM_CONTROL 0x00000648 +#define VIVS_FE_INDEX_STREAM_CONTROL_TYPE__MASK 0x00000003 +#define VIVS_FE_INDEX_STREAM_CONTROL_TYPE__SHIFT 0 +#define VIVS_FE_INDEX_STREAM_CONTROL_TYPE_UNSIGNED_CHAR 0x00000000 +#define VIVS_FE_INDEX_STREAM_CONTROL_TYPE_UNSIGNED_SHORT 0x00000001 +#define VIVS_FE_INDEX_STREAM_CONTROL_TYPE_UNSIGNED_INT 0x00000002 + +#define VIVS_FE_VERTEX_STREAM_BASE_ADDR 0x0000064c + +#define VIVS_FE_VERTEX_STREAM_CONTROL 0x00000650 + +#define VIVS_FE_COMMAND_ADDRESS 0x00000654 + +#define VIVS_FE_COMMAND_CONTROL 0x00000658 +#define VIVS_FE_COMMAND_CONTROL_PREFETCH__MASK 0x0000ffff +#define VIVS_FE_COMMAND_CONTROL_PREFETCH__SHIFT 0 +#define VIVS_FE_COMMAND_CONTROL_PREFETCH(x) (((x) << VIVS_FE_COMMAND_CONTROL_PREFETCH__SHIFT) & VIVS_FE_COMMAND_CONTROL_PREFETCH__MASK) +#define VIVS_FE_COMMAND_CONTROL_ENABLE 0x00010000 + +#define VIVS_FE_DMA_STATUS 0x0000065c + +#define VIVS_FE_DMA_DEBUG_STATE 0x00000660 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE__MASK 0x0000001f +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE__SHIFT 0 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_DEC 0x00000001 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_ADR0 0x00000002 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_LOAD0 0x00000003 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_ADR1 0x00000004 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_LOAD1 0x00000005 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_3DADR 0x00000006 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_3DCMD 0x00000007 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_3DCNTL 0x00000008 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_3DIDXCNTL 0x00000009 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_INITREQDMA 0x0000000a +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_DRAWIDX 0x0000000b +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_DRAW 0x0000000c +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_2DRECT0 0x0000000d +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_2DRECT1 0x0000000e +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_2DDATA0 0x0000000f +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_2DDATA1 0x00000010 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_WAITFIFO 0x00000011 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_WAIT 0x00000012 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_LINK 0x00000013 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_END 0x00000014 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_STATE_STALL 0x00000015 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE__MASK 0x00000300 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE__SHIFT 8 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE_START 0x00000100 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE_REQ 0x00000200 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_DMA_STATE_END 0x00000300 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_FETCH_STATE__MASK 0x00000c00 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_FETCH_STATE__SHIFT 10 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_FETCH_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_FETCH_STATE_RAMVALID 0x00000400 +#define VIVS_FE_DMA_DEBUG_STATE_CMD_FETCH_STATE_VALID 0x00000800 +#define VIVS_FE_DMA_DEBUG_STATE_REQ_DMA_STATE__MASK 0x00003000 +#define VIVS_FE_DMA_DEBUG_STATE_REQ_DMA_STATE__SHIFT 12 +#define VIVS_FE_DMA_DEBUG_STATE_REQ_DMA_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_REQ_DMA_STATE_WAITIDX 0x00001000 +#define VIVS_FE_DMA_DEBUG_STATE_REQ_DMA_STATE_CAL 0x00002000 +#define VIVS_FE_DMA_DEBUG_STATE_CAL_STATE__MASK 0x0000c000 +#define VIVS_FE_DMA_DEBUG_STATE_CAL_STATE__SHIFT 14 +#define VIVS_FE_DMA_DEBUG_STATE_CAL_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_CAL_STATE_LDADR 0x00004000 +#define VIVS_FE_DMA_DEBUG_STATE_CAL_STATE_IDXCALC 0x00008000 +#define VIVS_FE_DMA_DEBUG_STATE_VE_REQ_STATE__MASK 0x00030000 +#define VIVS_FE_DMA_DEBUG_STATE_VE_REQ_STATE__SHIFT 16 +#define VIVS_FE_DMA_DEBUG_STATE_VE_REQ_STATE_IDLE 0x00000000 +#define VIVS_FE_DMA_DEBUG_STATE_VE_REQ_STATE_CKCACHE 0x00010000 +#define VIVS_FE_DMA_DEBUG_STATE_VE_REQ_STATE_MISS 0x00020000 + +#define VIVS_FE_DMA_ADDRESS 0x00000664 + +#define VIVS_FE_DMA_LOW 0x00000668 + +#define VIVS_FE_DMA_HIGH 0x0000066c + +#define VIVS_FE_AUTO_FLUSH 0x00000670 + +#define VIVS_FE_UNK00678 0x00000678 + +#define VIVS_FE_UNK0067C 0x0000067c + +#define VIVS_FE_VERTEX_STREAMS(i0) (0x00000000 + 0x4*(i0)) +#define VIVS_FE_VERTEX_STREAMS__ESIZE 0x00000004 +#define VIVS_FE_VERTEX_STREAMS__LEN 0x00000008 + +#define VIVS_FE_VERTEX_STREAMS_BASE_ADDR(i0) (0x00000680 + 0x4*(i0)) + +#define VIVS_FE_VERTEX_STREAMS_CONTROL(i0) (0x000006a0 + 0x4*(i0)) + +#define VIVS_FE_UNK00700(i0) (0x00000700 + 0x4*(i0)) +#define VIVS_FE_UNK00700__ESIZE 0x00000004 +#define VIVS_FE_UNK00700__LEN 0x00000010 + +#define VIVS_FE_UNK00740(i0) (0x00000740 + 0x4*(i0)) +#define VIVS_FE_UNK00740__ESIZE 0x00000004 +#define VIVS_FE_UNK00740__LEN 0x00000010 + +#define VIVS_FE_UNK00780(i0) (0x00000780 + 0x4*(i0)) +#define VIVS_FE_UNK00780__ESIZE 0x00000004 +#define VIVS_FE_UNK00780__LEN 0x00000010 + +#define VIVS_GL 0x00000000 + +#define VIVS_GL_PIPE_SELECT 0x00003800 +#define VIVS_GL_PIPE_SELECT_PIPE__MASK 0x00000001 +#define VIVS_GL_PIPE_SELECT_PIPE__SHIFT 0 +#define VIVS_GL_PIPE_SELECT_PIPE(x) (((x) << VIVS_GL_PIPE_SELECT_PIPE__SHIFT) & VIVS_GL_PIPE_SELECT_PIPE__MASK) + +#define VIVS_GL_EVENT 0x00003804 +#define VIVS_GL_EVENT_EVENT_ID__MASK 0x0000001f +#define VIVS_GL_EVENT_EVENT_ID__SHIFT 0 +#define VIVS_GL_EVENT_EVENT_ID(x) (((x) << VIVS_GL_EVENT_EVENT_ID__SHIFT) & VIVS_GL_EVENT_EVENT_ID__MASK) +#define VIVS_GL_EVENT_FROM_FE 0x00000020 +#define VIVS_GL_EVENT_FROM_PE 0x00000040 +#define VIVS_GL_EVENT_SOURCE__MASK 0x00001f00 +#define VIVS_GL_EVENT_SOURCE__SHIFT 8 +#define VIVS_GL_EVENT_SOURCE(x) (((x) << VIVS_GL_EVENT_SOURCE__SHIFT) & VIVS_GL_EVENT_SOURCE__MASK) + +#define VIVS_GL_SEMAPHORE_TOKEN 0x00003808 +#define VIVS_GL_SEMAPHORE_TOKEN_FROM__MASK 0x0000001f +#define VIVS_GL_SEMAPHORE_TOKEN_FROM__SHIFT 0 +#define VIVS_GL_SEMAPHORE_TOKEN_FROM(x) (((x) << VIVS_GL_SEMAPHORE_TOKEN_FROM__SHIFT) & VIVS_GL_SEMAPHORE_TOKEN_FROM__MASK) +#define VIVS_GL_SEMAPHORE_TOKEN_TO__MASK 0x00001f00 +#define VIVS_GL_SEMAPHORE_TOKEN_TO__SHIFT 8 +#define VIVS_GL_SEMAPHORE_TOKEN_TO(x) (((x) << VIVS_GL_SEMAPHORE_TOKEN_TO__SHIFT) & VIVS_GL_SEMAPHORE_TOKEN_TO__MASK) + +#define VIVS_GL_FLUSH_CACHE 0x0000380c +#define VIVS_GL_FLUSH_CACHE_DEPTH 0x00000001 +#define VIVS_GL_FLUSH_CACHE_COLOR 0x00000002 +#define VIVS_GL_FLUSH_CACHE_TEXTURE 0x00000004 +#define VIVS_GL_FLUSH_CACHE_PE2D 0x00000008 +#define VIVS_GL_FLUSH_CACHE_TEXTUREVS 0x00000010 +#define VIVS_GL_FLUSH_CACHE_SHADER_L1 0x00000020 +#define VIVS_GL_FLUSH_CACHE_SHADER_L2 0x00000040 + +#define VIVS_GL_FLUSH_MMU 0x00003810 +#define VIVS_GL_FLUSH_MMU_FLUSH_FEMMU 0x00000001 +#define VIVS_GL_FLUSH_MMU_FLUSH_UNK1 0x00000002 +#define VIVS_GL_FLUSH_MMU_FLUSH_UNK2 0x00000004 +#define VIVS_GL_FLUSH_MMU_FLUSH_PEMMU 0x00000008 +#define VIVS_GL_FLUSH_MMU_FLUSH_UNK4 0x00000010 + +#define VIVS_GL_VERTEX_ELEMENT_CONFIG 0x00003814 + +#define VIVS_GL_MULTI_SAMPLE_CONFIG 0x00003818 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES__MASK 0x00000003 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES__SHIFT 0 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES_NONE 0x00000000 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES_2X 0x00000001 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES_4X 0x00000002 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_SAMPLES_MASK 0x00000008 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES__MASK 0x000000f0 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES__SHIFT 4 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES(x) (((x) << VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES__SHIFT) & VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES__MASK) +#define VIVS_GL_MULTI_SAMPLE_CONFIG_MSAA_ENABLES_MASK 0x00000100 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12__MASK 0x00007000 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12__SHIFT 12 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12(x) (((x) << VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12__SHIFT) & VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12__MASK) +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK12_MASK 0x00008000 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16__MASK 0x00030000 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16__SHIFT 16 +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16(x) (((x) << VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16__SHIFT) & VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16__MASK) +#define VIVS_GL_MULTI_SAMPLE_CONFIG_UNK16_MASK 0x00080000 + +#define VIVS_GL_VARYING_TOTAL_COMPONENTS 0x0000381c +#define VIVS_GL_VARYING_TOTAL_COMPONENTS_NUM__MASK 0x000000ff +#define VIVS_GL_VARYING_TOTAL_COMPONENTS_NUM__SHIFT 0 +#define VIVS_GL_VARYING_TOTAL_COMPONENTS_NUM(x) (((x) << VIVS_GL_VARYING_TOTAL_COMPONENTS_NUM__SHIFT) & VIVS_GL_VARYING_TOTAL_COMPONENTS_NUM__MASK) + +#define VIVS_GL_VARYING_NUM_COMPONENTS 0x00003820 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR0__MASK 0x00000007 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR0__SHIFT 0 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR0(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR0__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR0__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR1__MASK 0x00000070 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR1__SHIFT 4 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR1(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR1__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR1__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR2__MASK 0x00000700 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR2__SHIFT 8 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR2(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR2__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR2__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR3__MASK 0x00007000 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR3__SHIFT 12 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR3(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR3__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR3__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR4__MASK 0x00070000 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR4__SHIFT 16 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR4(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR4__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR4__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR5__MASK 0x00700000 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR5__SHIFT 20 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR5(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR5__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR5__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR6__MASK 0x07000000 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR6__SHIFT 24 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR6(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR6__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR6__MASK) +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR7__MASK 0x70000000 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR7__SHIFT 28 +#define VIVS_GL_VARYING_NUM_COMPONENTS_VAR7(x) (((x) << VIVS_GL_VARYING_NUM_COMPONENTS_VAR7__SHIFT) & VIVS_GL_VARYING_NUM_COMPONENTS_VAR7__MASK) + +#define VIVS_GL_VARYING_COMPONENT_USE(i0) (0x00003828 + 0x4*(i0)) +#define VIVS_GL_VARYING_COMPONENT_USE__ESIZE 0x00000004 +#define VIVS_GL_VARYING_COMPONENT_USE__LEN 0x00000002 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP0__MASK 0x00000003 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP0__SHIFT 0 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP0(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP0__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP0__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP1__MASK 0x0000000c +#define VIVS_GL_VARYING_COMPONENT_USE_COMP1__SHIFT 2 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP1(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP1__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP1__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP2__MASK 0x00000030 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP2__SHIFT 4 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP2(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP2__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP2__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP3__MASK 0x000000c0 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP3__SHIFT 6 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP3(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP3__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP3__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP4__MASK 0x00000300 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP4__SHIFT 8 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP4(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP4__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP4__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP5__MASK 0x00000c00 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP5__SHIFT 10 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP5(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP5__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP5__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP6__MASK 0x00003000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP6__SHIFT 12 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP6(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP6__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP6__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP7__MASK 0x0000c000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP7__SHIFT 14 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP7(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP7__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP7__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP8__MASK 0x00030000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP8__SHIFT 16 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP8(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP8__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP8__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP9__MASK 0x000c0000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP9__SHIFT 18 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP9(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP9__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP9__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP10__MASK 0x00300000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP10__SHIFT 20 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP10(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP10__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP10__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP11__MASK 0x00c00000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP11__SHIFT 22 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP11(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP11__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP11__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP12__MASK 0x03000000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP12__SHIFT 24 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP12(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP12__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP12__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP13__MASK 0x0c000000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP13__SHIFT 26 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP13(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP13__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP13__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP14__MASK 0x30000000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP14__SHIFT 28 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP14(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP14__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP14__MASK) +#define VIVS_GL_VARYING_COMPONENT_USE_COMP15__MASK 0xc0000000 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP15__SHIFT 30 +#define VIVS_GL_VARYING_COMPONENT_USE_COMP15(x) (((x) << VIVS_GL_VARYING_COMPONENT_USE_COMP15__SHIFT) & VIVS_GL_VARYING_COMPONENT_USE_COMP15__MASK) + +#define VIVS_GL_UNK03834 0x00003834 + +#define VIVS_GL_UNK03838 0x00003838 + +#define VIVS_GL_API_MODE 0x0000384c +#define VIVS_GL_API_MODE_OPENGL 0x00000000 +#define VIVS_GL_API_MODE_OPENVG 0x00000001 +#define VIVS_GL_API_MODE_OPENCL 0x00000002 + +#define VIVS_GL_CONTEXT_POINTER 0x00003850 + +#define VIVS_GL_UNK03A00 0x00003a00 + +#define VIVS_GL_STALL_TOKEN 0x00003c00 +#define VIVS_GL_STALL_TOKEN_FROM__MASK 0x0000001f +#define VIVS_GL_STALL_TOKEN_FROM__SHIFT 0 +#define VIVS_GL_STALL_TOKEN_FROM(x) (((x) << VIVS_GL_STALL_TOKEN_FROM__SHIFT) & VIVS_GL_STALL_TOKEN_FROM__MASK) +#define VIVS_GL_STALL_TOKEN_TO__MASK 0x00001f00 +#define VIVS_GL_STALL_TOKEN_TO__SHIFT 8 +#define VIVS_GL_STALL_TOKEN_TO(x) (((x) << VIVS_GL_STALL_TOKEN_TO__SHIFT) & VIVS_GL_STALL_TOKEN_TO__MASK) +#define VIVS_GL_STALL_TOKEN_FLIP0 0x40000000 +#define VIVS_GL_STALL_TOKEN_FLIP1 0x80000000 + +#define VIVS_DUMMY 0x00000000 + +#define VIVS_DUMMY_DUMMY 0x0003fffc + + +#endif /* STATE_XML */ diff --git b/drivers/gpu/drm/etnaviv/state_hi.xml.h b/drivers/gpu/drm/etnaviv/state_hi.xml.h new file mode 100644 index 0000000..6a7de5f --- /dev/null +++ b/drivers/gpu/drm/etnaviv/state_hi.xml.h @@ -0,0 +1,429 @@ +#ifndef STATE_HI_XML +#define STATE_HI_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://0x04.net/cgit/index.cgi/rules-ng-ng +git clone git://0x04.net/rules-ng-ng + +The rules-ng-ng source files this header was generated from are: +- state_hi.xml ( 24309 bytes, from 2015-12-12 09:02:53) +- common.xml ( 18437 bytes, from 2015-12-12 09:02:53) + +Copyright (C) 2015 +*/ + + +#define MMU_EXCEPTION_SLAVE_NOT_PRESENT 0x00000001 +#define MMU_EXCEPTION_PAGE_NOT_PRESENT 0x00000002 +#define MMU_EXCEPTION_WRITE_VIOLATION 0x00000003 +#define VIVS_HI 0x00000000 + +#define VIVS_HI_CLOCK_CONTROL 0x00000000 +#define VIVS_HI_CLOCK_CONTROL_CLK3D_DIS 0x00000001 +#define VIVS_HI_CLOCK_CONTROL_CLK2D_DIS 0x00000002 +#define VIVS_HI_CLOCK_CONTROL_FSCALE_VAL__MASK 0x000001fc +#define VIVS_HI_CLOCK_CONTROL_FSCALE_VAL__SHIFT 2 +#define VIVS_HI_CLOCK_CONTROL_FSCALE_VAL(x) (((x) << VIVS_HI_CLOCK_CONTROL_FSCALE_VAL__SHIFT) & VIVS_HI_CLOCK_CONTROL_FSCALE_VAL__MASK) +#define VIVS_HI_CLOCK_CONTROL_FSCALE_CMD_LOAD 0x00000200 +#define VIVS_HI_CLOCK_CONTROL_DISABLE_RAM_CLK_GATING 0x00000400 +#define VIVS_HI_CLOCK_CONTROL_DISABLE_DEBUG_REGISTERS 0x00000800 +#define VIVS_HI_CLOCK_CONTROL_SOFT_RESET 0x00001000 +#define VIVS_HI_CLOCK_CONTROL_IDLE_3D 0x00010000 +#define VIVS_HI_CLOCK_CONTROL_IDLE_2D 0x00020000 +#define VIVS_HI_CLOCK_CONTROL_IDLE_VG 0x00040000 +#define VIVS_HI_CLOCK_CONTROL_ISOLATE_GPU 0x00080000 +#define VIVS_HI_CLOCK_CONTROL_DEBUG_PIXEL_PIPE__MASK 0x00f00000 +#define VIVS_HI_CLOCK_CONTROL_DEBUG_PIXEL_PIPE__SHIFT 20 +#define VIVS_HI_CLOCK_CONTROL_DEBUG_PIXEL_PIPE(x) (((x) << VIVS_HI_CLOCK_CONTROL_DEBUG_PIXEL_PIPE__SHIFT) & VIVS_HI_CLOCK_CONTROL_DEBUG_PIXEL_PIPE__MASK) + +#define VIVS_HI_IDLE_STATE 0x00000004 +#define VIVS_HI_IDLE_STATE_FE 0x00000001 +#define VIVS_HI_IDLE_STATE_DE 0x00000002 +#define VIVS_HI_IDLE_STATE_PE 0x00000004 +#define VIVS_HI_IDLE_STATE_SH 0x00000008 +#define VIVS_HI_IDLE_STATE_PA 0x00000010 +#define VIVS_HI_IDLE_STATE_SE 0x00000020 +#define VIVS_HI_IDLE_STATE_RA 0x00000040 +#define VIVS_HI_IDLE_STATE_TX 0x00000080 +#define VIVS_HI_IDLE_STATE_VG 0x00000100 +#define VIVS_HI_IDLE_STATE_IM 0x00000200 +#define VIVS_HI_IDLE_STATE_FP 0x00000400 +#define VIVS_HI_IDLE_STATE_TS 0x00000800 +#define VIVS_HI_IDLE_STATE_AXI_LP 0x80000000 + +#define VIVS_HI_AXI_CONFIG 0x00000008 +#define VIVS_HI_AXI_CONFIG_AWID__MASK 0x0000000f +#define VIVS_HI_AXI_CONFIG_AWID__SHIFT 0 +#define VIVS_HI_AXI_CONFIG_AWID(x) (((x) << VIVS_HI_AXI_CONFIG_AWID__SHIFT) & VIVS_HI_AXI_CONFIG_AWID__MASK) +#define VIVS_HI_AXI_CONFIG_ARID__MASK 0x000000f0 +#define VIVS_HI_AXI_CONFIG_ARID__SHIFT 4 +#define VIVS_HI_AXI_CONFIG_ARID(x) (((x) << VIVS_HI_AXI_CONFIG_ARID__SHIFT) & VIVS_HI_AXI_CONFIG_ARID__MASK) +#define VIVS_HI_AXI_CONFIG_AWCACHE__MASK 0x00000f00 +#define VIVS_HI_AXI_CONFIG_AWCACHE__SHIFT 8 +#define VIVS_HI_AXI_CONFIG_AWCACHE(x) (((x) << VIVS_HI_AXI_CONFIG_AWCACHE__SHIFT) & VIVS_HI_AXI_CONFIG_AWCACHE__MASK) +#define VIVS_HI_AXI_CONFIG_ARCACHE__MASK 0x0000f000 +#define VIVS_HI_AXI_CONFIG_ARCACHE__SHIFT 12 +#define VIVS_HI_AXI_CONFIG_ARCACHE(x) (((x) << VIVS_HI_AXI_CONFIG_ARCACHE__SHIFT) & VIVS_HI_AXI_CONFIG_ARCACHE__MASK) + +#define VIVS_HI_AXI_STATUS 0x0000000c +#define VIVS_HI_AXI_STATUS_WR_ERR_ID__MASK 0x0000000f +#define VIVS_HI_AXI_STATUS_WR_ERR_ID__SHIFT 0 +#define VIVS_HI_AXI_STATUS_WR_ERR_ID(x) (((x) << VIVS_HI_AXI_STATUS_WR_ERR_ID__SHIFT) & VIVS_HI_AXI_STATUS_WR_ERR_ID__MASK) +#define VIVS_HI_AXI_STATUS_RD_ERR_ID__MASK 0x000000f0 +#define VIVS_HI_AXI_STATUS_RD_ERR_ID__SHIFT 4 +#define VIVS_HI_AXI_STATUS_RD_ERR_ID(x) (((x) << VIVS_HI_AXI_STATUS_RD_ERR_ID__SHIFT) & VIVS_HI_AXI_STATUS_RD_ERR_ID__MASK) +#define VIVS_HI_AXI_STATUS_DET_WR_ERR 0x00000100 +#define VIVS_HI_AXI_STATUS_DET_RD_ERR 0x00000200 + +#define VIVS_HI_INTR_ACKNOWLEDGE 0x00000010 +#define VIVS_HI_INTR_ACKNOWLEDGE_INTR_VEC__MASK 0x7fffffff +#define VIVS_HI_INTR_ACKNOWLEDGE_INTR_VEC__SHIFT 0 +#define VIVS_HI_INTR_ACKNOWLEDGE_INTR_VEC(x) (((x) << VIVS_HI_INTR_ACKNOWLEDGE_INTR_VEC__SHIFT) & VIVS_HI_INTR_ACKNOWLEDGE_INTR_VEC__MASK) +#define VIVS_HI_INTR_ACKNOWLEDGE_AXI_BUS_ERROR 0x80000000 + +#define VIVS_HI_INTR_ENBL 0x00000014 +#define VIVS_HI_INTR_ENBL_INTR_ENBL_VEC__MASK 0xffffffff +#define VIVS_HI_INTR_ENBL_INTR_ENBL_VEC__SHIFT 0 +#define VIVS_HI_INTR_ENBL_INTR_ENBL_VEC(x) (((x) << VIVS_HI_INTR_ENBL_INTR_ENBL_VEC__SHIFT) & VIVS_HI_INTR_ENBL_INTR_ENBL_VEC__MASK) + +#define VIVS_HI_CHIP_IDENTITY 0x00000018 +#define VIVS_HI_CHIP_IDENTITY_FAMILY__MASK 0xff000000 +#define VIVS_HI_CHIP_IDENTITY_FAMILY__SHIFT 24 +#define VIVS_HI_CHIP_IDENTITY_FAMILY(x) (((x) << VIVS_HI_CHIP_IDENTITY_FAMILY__SHIFT) & VIVS_HI_CHIP_IDENTITY_FAMILY__MASK) +#define VIVS_HI_CHIP_IDENTITY_PRODUCT__MASK 0x00ff0000 +#define VIVS_HI_CHIP_IDENTITY_PRODUCT__SHIFT 16 +#define VIVS_HI_CHIP_IDENTITY_PRODUCT(x) (((x) << VIVS_HI_CHIP_IDENTITY_PRODUCT__SHIFT) & VIVS_HI_CHIP_IDENTITY_PRODUCT__MASK) +#define VIVS_HI_CHIP_IDENTITY_REVISION__MASK 0x0000f000 +#define VIVS_HI_CHIP_IDENTITY_REVISION__SHIFT 12 +#define VIVS_HI_CHIP_IDENTITY_REVISION(x) (((x) << VIVS_HI_CHIP_IDENTITY_REVISION__SHIFT) & VIVS_HI_CHIP_IDENTITY_REVISION__MASK) + +#define VIVS_HI_CHIP_FEATURE 0x0000001c + +#define VIVS_HI_CHIP_MODEL 0x00000020 + +#define VIVS_HI_CHIP_REV 0x00000024 + +#define VIVS_HI_CHIP_DATE 0x00000028 + +#define VIVS_HI_CHIP_TIME 0x0000002c + +#define VIVS_HI_CHIP_MINOR_FEATURE_0 0x00000034 + +#define VIVS_HI_CACHE_CONTROL 0x00000038 + +#define VIVS_HI_MEMORY_COUNTER_RESET 0x0000003c + +#define VIVS_HI_PROFILE_READ_BYTES8 0x00000040 + +#define VIVS_HI_PROFILE_WRITE_BYTES8 0x00000044 + +#define VIVS_HI_CHIP_SPECS 0x00000048 +#define VIVS_HI_CHIP_SPECS_STREAM_COUNT__MASK 0x0000000f +#define VIVS_HI_CHIP_SPECS_STREAM_COUNT__SHIFT 0 +#define VIVS_HI_CHIP_SPECS_STREAM_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_STREAM_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_STREAM_COUNT__MASK) +#define VIVS_HI_CHIP_SPECS_REGISTER_MAX__MASK 0x000000f0 +#define VIVS_HI_CHIP_SPECS_REGISTER_MAX__SHIFT 4 +#define VIVS_HI_CHIP_SPECS_REGISTER_MAX(x) (((x) << VIVS_HI_CHIP_SPECS_REGISTER_MAX__SHIFT) & VIVS_HI_CHIP_SPECS_REGISTER_MAX__MASK) +#define VIVS_HI_CHIP_SPECS_THREAD_COUNT__MASK 0x00000f00 +#define VIVS_HI_CHIP_SPECS_THREAD_COUNT__SHIFT 8 +#define VIVS_HI_CHIP_SPECS_THREAD_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_THREAD_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_THREAD_COUNT__MASK) +#define VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE__MASK 0x0001f000 +#define VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE__SHIFT 12 +#define VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE(x) (((x) << VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE__SHIFT) & VIVS_HI_CHIP_SPECS_VERTEX_CACHE_SIZE__MASK) +#define VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT__MASK 0x01f00000 +#define VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT__SHIFT 20 +#define VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_SHADER_CORE_COUNT__MASK) +#define VIVS_HI_CHIP_SPECS_PIXEL_PIPES__MASK 0x0e000000 +#define VIVS_HI_CHIP_SPECS_PIXEL_PIPES__SHIFT 25 +#define VIVS_HI_CHIP_SPECS_PIXEL_PIPES(x) (((x) << VIVS_HI_CHIP_SPECS_PIXEL_PIPES__SHIFT) & VIVS_HI_CHIP_SPECS_PIXEL_PIPES__MASK) +#define VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE__MASK 0xf0000000 +#define VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE__SHIFT 28 +#define VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE(x) (((x) << VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE__SHIFT) & VIVS_HI_CHIP_SPECS_VERTEX_OUTPUT_BUFFER_SIZE__MASK) + +#define VIVS_HI_PROFILE_WRITE_BURSTS 0x0000004c + +#define VIVS_HI_PROFILE_WRITE_REQUESTS 0x00000050 + +#define VIVS_HI_PROFILE_READ_BURSTS 0x00000058 + +#define VIVS_HI_PROFILE_READ_REQUESTS 0x0000005c + +#define VIVS_HI_PROFILE_READ_LASTS 0x00000060 + +#define VIVS_HI_GP_OUT0 0x00000064 + +#define VIVS_HI_GP_OUT1 0x00000068 + +#define VIVS_HI_GP_OUT2 0x0000006c + +#define VIVS_HI_AXI_CONTROL 0x00000070 +#define VIVS_HI_AXI_CONTROL_WR_FULL_BURST_MODE 0x00000001 + +#define VIVS_HI_CHIP_MINOR_FEATURE_1 0x00000074 + +#define VIVS_HI_PROFILE_TOTAL_CYCLES 0x00000078 + +#define VIVS_HI_PROFILE_IDLE_CYCLES 0x0000007c + +#define VIVS_HI_CHIP_SPECS_2 0x00000080 +#define VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE__MASK 0x000000ff +#define VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE__SHIFT 0 +#define VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE(x) (((x) << VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE__SHIFT) & VIVS_HI_CHIP_SPECS_2_BUFFER_SIZE__MASK) +#define VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT__MASK 0x0000ff00 +#define VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT__SHIFT 8 +#define VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_2_INSTRUCTION_COUNT__MASK) +#define VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS__MASK 0xffff0000 +#define VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS__SHIFT 16 +#define VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS(x) (((x) << VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS__SHIFT) & VIVS_HI_CHIP_SPECS_2_NUM_CONSTANTS__MASK) + +#define VIVS_HI_CHIP_MINOR_FEATURE_2 0x00000084 + +#define VIVS_HI_CHIP_MINOR_FEATURE_3 0x00000088 + +#define VIVS_HI_CHIP_SPECS_3 0x0000008c +#define VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT__MASK 0x000001f0 +#define VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT__SHIFT 4 +#define VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_3_VARYINGS_COUNT__MASK) +#define VIVS_HI_CHIP_SPECS_3_GPU_CORE_COUNT__MASK 0x00000007 +#define VIVS_HI_CHIP_SPECS_3_GPU_CORE_COUNT__SHIFT 0 +#define VIVS_HI_CHIP_SPECS_3_GPU_CORE_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_3_GPU_CORE_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_3_GPU_CORE_COUNT__MASK) + +#define VIVS_HI_CHIP_MINOR_FEATURE_4 0x00000094 + +#define VIVS_HI_CHIP_SPECS_4 0x0000009c +#define VIVS_HI_CHIP_SPECS_4_STREAM_COUNT__MASK 0x0001f000 +#define VIVS_HI_CHIP_SPECS_4_STREAM_COUNT__SHIFT 12 +#define VIVS_HI_CHIP_SPECS_4_STREAM_COUNT(x) (((x) << VIVS_HI_CHIP_SPECS_4_STREAM_COUNT__SHIFT) & VIVS_HI_CHIP_SPECS_4_STREAM_COUNT__MASK) + +#define VIVS_HI_CHIP_MINOR_FEATURE_5 0x000000a0 + +#define VIVS_HI_CHIP_PRODUCT_ID 0x000000a8 + +#define VIVS_PM 0x00000000 + +#define VIVS_PM_POWER_CONTROLS 0x00000100 +#define VIVS_PM_POWER_CONTROLS_ENABLE_MODULE_CLOCK_GATING 0x00000001 +#define VIVS_PM_POWER_CONTROLS_DISABLE_STALL_MODULE_CLOCK_GATING 0x00000002 +#define VIVS_PM_POWER_CONTROLS_DISABLE_STARVE_MODULE_CLOCK_GATING 0x00000004 +#define VIVS_PM_POWER_CONTROLS_TURN_ON_COUNTER__MASK 0x000000f0 +#define VIVS_PM_POWER_CONTROLS_TURN_ON_COUNTER__SHIFT 4 +#define VIVS_PM_POWER_CONTROLS_TURN_ON_COUNTER(x) (((x) << VIVS_PM_POWER_CONTROLS_TURN_ON_COUNTER__SHIFT) & VIVS_PM_POWER_CONTROLS_TURN_ON_COUNTER__MASK) +#define VIVS_PM_POWER_CONTROLS_TURN_OFF_COUNTER__MASK 0xffff0000 +#define VIVS_PM_POWER_CONTROLS_TURN_OFF_COUNTER__SHIFT 16 +#define VIVS_PM_POWER_CONTROLS_TURN_OFF_COUNTER(x) (((x) << VIVS_PM_POWER_CONTROLS_TURN_OFF_COUNTER__SHIFT) & VIVS_PM_POWER_CONTROLS_TURN_OFF_COUNTER__MASK) + +#define VIVS_PM_MODULE_CONTROLS 0x00000104 +#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_FE 0x00000001 +#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_DE 0x00000002 +#define VIVS_PM_MODULE_CONTROLS_DISABLE_MODULE_CLOCK_GATING_PE 0x00000004 + +#define VIVS_PM_MODULE_STATUS 0x00000108 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_FE 0x00000001 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_DE 0x00000002 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_PE 0x00000004 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_SH 0x00000008 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_PA 0x00000010 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_SE 0x00000020 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_RA 0x00000040 +#define VIVS_PM_MODULE_STATUS_MODULE_CLOCK_GATED_TX 0x00000080 + +#define VIVS_PM_PULSE_EATER 0x0000010c + +#define VIVS_MMUv2 0x00000000 + +#define VIVS_MMUv2_SAFE_ADDRESS 0x00000180 + +#define VIVS_MMUv2_CONFIGURATION 0x00000184 +#define VIVS_MMUv2_CONFIGURATION_MODE__MASK 0x00000001 +#define VIVS_MMUv2_CONFIGURATION_MODE__SHIFT 0 +#define VIVS_MMUv2_CONFIGURATION_MODE_MODE4_K 0x00000000 +#define VIVS_MMUv2_CONFIGURATION_MODE_MODE1_K 0x00000001 +#define VIVS_MMUv2_CONFIGURATION_MODE_MASK 0x00000008 +#define VIVS_MMUv2_CONFIGURATION_FLUSH__MASK 0x00000010 +#define VIVS_MMUv2_CONFIGURATION_FLUSH__SHIFT 4 +#define VIVS_MMUv2_CONFIGURATION_FLUSH_FLUSH 0x00000010 +#define VIVS_MMUv2_CONFIGURATION_FLUSH_MASK 0x00000080 +#define VIVS_MMUv2_CONFIGURATION_ADDRESS_MASK 0x00000100 +#define VIVS_MMUv2_CONFIGURATION_ADDRESS__MASK 0xfffffc00 +#define VIVS_MMUv2_CONFIGURATION_ADDRESS__SHIFT 10 +#define VIVS_MMUv2_CONFIGURATION_ADDRESS(x) (((x) << VIVS_MMUv2_CONFIGURATION_ADDRESS__SHIFT) & VIVS_MMUv2_CONFIGURATION_ADDRESS__MASK) + +#define VIVS_MMUv2_STATUS 0x00000188 +#define VIVS_MMUv2_STATUS_EXCEPTION0__MASK 0x00000003 +#define VIVS_MMUv2_STATUS_EXCEPTION0__SHIFT 0 +#define VIVS_MMUv2_STATUS_EXCEPTION0(x) (((x) << VIVS_MMUv2_STATUS_EXCEPTION0__SHIFT) & VIVS_MMUv2_STATUS_EXCEPTION0__MASK) +#define VIVS_MMUv2_STATUS_EXCEPTION1__MASK 0x00000030 +#define VIVS_MMUv2_STATUS_EXCEPTION1__SHIFT 4 +#define VIVS_MMUv2_STATUS_EXCEPTION1(x) (((x) << VIVS_MMUv2_STATUS_EXCEPTION1__SHIFT) & VIVS_MMUv2_STATUS_EXCEPTION1__MASK) +#define VIVS_MMUv2_STATUS_EXCEPTION2__MASK 0x00000300 +#define VIVS_MMUv2_STATUS_EXCEPTION2__SHIFT 8 +#define VIVS_MMUv2_STATUS_EXCEPTION2(x) (((x) << VIVS_MMUv2_STATUS_EXCEPTION2__SHIFT) & VIVS_MMUv2_STATUS_EXCEPTION2__MASK) +#define VIVS_MMUv2_STATUS_EXCEPTION3__MASK 0x00003000 +#define VIVS_MMUv2_STATUS_EXCEPTION3__SHIFT 12 +#define VIVS_MMUv2_STATUS_EXCEPTION3(x) (((x) << VIVS_MMUv2_STATUS_EXCEPTION3__SHIFT) & VIVS_MMUv2_STATUS_EXCEPTION3__MASK) + +#define VIVS_MMUv2_CONTROL 0x0000018c +#define VIVS_MMUv2_CONTROL_ENABLE 0x00000001 + +#define VIVS_MMUv2_EXCEPTION_ADDR(i0) (0x00000190 + 0x4*(i0)) +#define VIVS_MMUv2_EXCEPTION_ADDR__ESIZE 0x00000004 +#define VIVS_MMUv2_EXCEPTION_ADDR__LEN 0x00000004 + +#define VIVS_MC 0x00000000 + +#define VIVS_MC_MMU_FE_PAGE_TABLE 0x00000400 + +#define VIVS_MC_MMU_TX_PAGE_TABLE 0x00000404 + +#define VIVS_MC_MMU_PE_PAGE_TABLE 0x00000408 + +#define VIVS_MC_MMU_PEZ_PAGE_TABLE 0x0000040c + +#define VIVS_MC_MMU_RA_PAGE_TABLE 0x00000410 + +#define VIVS_MC_DEBUG_MEMORY 0x00000414 +#define VIVS_MC_DEBUG_MEMORY_SPECIAL_PATCH_GC320 0x00000008 +#define VIVS_MC_DEBUG_MEMORY_FAST_CLEAR_BYPASS 0x00100000 +#define VIVS_MC_DEBUG_MEMORY_COMPRESSION_BYPASS 0x00200000 + +#define VIVS_MC_MEMORY_BASE_ADDR_RA 0x00000418 + +#define VIVS_MC_MEMORY_BASE_ADDR_FE 0x0000041c + +#define VIVS_MC_MEMORY_BASE_ADDR_TX 0x00000420 + +#define VIVS_MC_MEMORY_BASE_ADDR_PEZ 0x00000424 + +#define VIVS_MC_MEMORY_BASE_ADDR_PE 0x00000428 + +#define VIVS_MC_MEMORY_TIMING_CONTROL 0x0000042c + +#define VIVS_MC_MEMORY_FLUSH 0x00000430 + +#define VIVS_MC_PROFILE_CYCLE_COUNTER 0x00000438 + +#define VIVS_MC_DEBUG_READ0 0x0000043c + +#define VIVS_MC_DEBUG_READ1 0x00000440 + +#define VIVS_MC_DEBUG_WRITE 0x00000444 + +#define VIVS_MC_PROFILE_RA_READ 0x00000448 + +#define VIVS_MC_PROFILE_TX_READ 0x0000044c + +#define VIVS_MC_PROFILE_FE_READ 0x00000450 + +#define VIVS_MC_PROFILE_PE_READ 0x00000454 + +#define VIVS_MC_PROFILE_DE_READ 0x00000458 + +#define VIVS_MC_PROFILE_SH_READ 0x0000045c + +#define VIVS_MC_PROFILE_PA_READ 0x00000460 + +#define VIVS_MC_PROFILE_SE_READ 0x00000464 + +#define VIVS_MC_PROFILE_MC_READ 0x00000468 + +#define VIVS_MC_PROFILE_HI_READ 0x0000046c + +#define VIVS_MC_PROFILE_CONFIG0 0x00000470 +#define VIVS_MC_PROFILE_CONFIG0_FE__MASK 0x0000000f +#define VIVS_MC_PROFILE_CONFIG0_FE__SHIFT 0 +#define VIVS_MC_PROFILE_CONFIG0_FE_RESET 0x0000000f +#define VIVS_MC_PROFILE_CONFIG0_DE__MASK 0x00000f00 +#define VIVS_MC_PROFILE_CONFIG0_DE__SHIFT 8 +#define VIVS_MC_PROFILE_CONFIG0_DE_RESET 0x00000f00 +#define VIVS_MC_PROFILE_CONFIG0_PE__MASK 0x000f0000 +#define VIVS_MC_PROFILE_CONFIG0_PE__SHIFT 16 +#define VIVS_MC_PROFILE_CONFIG0_PE_PIXEL_COUNT_KILLED_BY_COLOR_PIPE 0x00000000 +#define VIVS_MC_PROFILE_CONFIG0_PE_PIXEL_COUNT_KILLED_BY_DEPTH_PIPE 0x00010000 +#define VIVS_MC_PROFILE_CONFIG0_PE_PIXEL_COUNT_DRAWN_BY_COLOR_PIPE 0x00020000 +#define VIVS_MC_PROFILE_CONFIG0_PE_PIXEL_COUNT_DRAWN_BY_DEPTH_PIPE 0x00030000 +#define VIVS_MC_PROFILE_CONFIG0_PE_PIXELS_RENDERED_2D 0x000b0000 +#define VIVS_MC_PROFILE_CONFIG0_PE_RESET 0x000f0000 +#define VIVS_MC_PROFILE_CONFIG0_SH__MASK 0x0f000000 +#define VIVS_MC_PROFILE_CONFIG0_SH__SHIFT 24 +#define VIVS_MC_PROFILE_CONFIG0_SH_SHADER_CYCLES 0x04000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_PS_INST_COUNTER 0x07000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_RENDERED_PIXEL_COUNTER 0x08000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_VS_INST_COUNTER 0x09000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_RENDERED_VERTICE_COUNTER 0x0a000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_VTX_BRANCH_INST_COUNTER 0x0b000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_VTX_TEXLD_INST_COUNTER 0x0c000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_PXL_BRANCH_INST_COUNTER 0x0d000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_PXL_TEXLD_INST_COUNTER 0x0e000000 +#define VIVS_MC_PROFILE_CONFIG0_SH_RESET 0x0f000000 + +#define VIVS_MC_PROFILE_CONFIG1 0x00000474 +#define VIVS_MC_PROFILE_CONFIG1_PA__MASK 0x0000000f +#define VIVS_MC_PROFILE_CONFIG1_PA__SHIFT 0 +#define VIVS_MC_PROFILE_CONFIG1_PA_INPUT_VTX_COUNTER 0x00000003 +#define VIVS_MC_PROFILE_CONFIG1_PA_INPUT_PRIM_COUNTER 0x00000004 +#define VIVS_MC_PROFILE_CONFIG1_PA_OUTPUT_PRIM_COUNTER 0x00000005 +#define VIVS_MC_PROFILE_CONFIG1_PA_DEPTH_CLIPPED_COUNTER 0x00000006 +#define VIVS_MC_PROFILE_CONFIG1_PA_TRIVIAL_REJECTED_COUNTER 0x00000007 +#define VIVS_MC_PROFILE_CONFIG1_PA_CULLED_COUNTER 0x00000008 +#define VIVS_MC_PROFILE_CONFIG1_PA_RESET 0x0000000f +#define VIVS_MC_PROFILE_CONFIG1_SE__MASK 0x00000f00 +#define VIVS_MC_PROFILE_CONFIG1_SE__SHIFT 8 +#define VIVS_MC_PROFILE_CONFIG1_SE_CULLED_TRIANGLE_COUNT 0x00000000 +#define VIVS_MC_PROFILE_CONFIG1_SE_CULLED_LINES_COUNT 0x00000100 +#define VIVS_MC_PROFILE_CONFIG1_SE_RESET 0x00000f00 +#define VIVS_MC_PROFILE_CONFIG1_RA__MASK 0x000f0000 +#define VIVS_MC_PROFILE_CONFIG1_RA__SHIFT 16 +#define VIVS_MC_PROFILE_CONFIG1_RA_VALID_PIXEL_COUNT 0x00000000 +#define VIVS_MC_PROFILE_CONFIG1_RA_TOTAL_QUAD_COUNT 0x00010000 +#define VIVS_MC_PROFILE_CONFIG1_RA_VALID_QUAD_COUNT_AFTER_EARLY_Z 0x00020000 +#define VIVS_MC_PROFILE_CONFIG1_RA_TOTAL_PRIMITIVE_COUNT 0x00030000 +#define VIVS_MC_PROFILE_CONFIG1_RA_PIPE_CACHE_MISS_COUNTER 0x00090000 +#define VIVS_MC_PROFILE_CONFIG1_RA_PREFETCH_CACHE_MISS_COUNTER 0x000a0000 +#define VIVS_MC_PROFILE_CONFIG1_RA_CULLED_QUAD_COUNT 0x000b0000 +#define VIVS_MC_PROFILE_CONFIG1_RA_RESET 0x000f0000 +#define VIVS_MC_PROFILE_CONFIG1_TX__MASK 0x0f000000 +#define VIVS_MC_PROFILE_CONFIG1_TX__SHIFT 24 +#define VIVS_MC_PROFILE_CONFIG1_TX_TOTAL_BILINEAR_REQUESTS 0x00000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_TOTAL_TRILINEAR_REQUESTS 0x01000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_TOTAL_DISCARDED_TEXTURE_REQUESTS 0x02000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_TOTAL_TEXTURE_REQUESTS 0x03000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_UNKNOWN 0x04000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_MEM_READ_COUNT 0x05000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_MEM_READ_IN_8B_COUNT 0x06000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_CACHE_MISS_COUNT 0x07000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_CACHE_HIT_TEXEL_COUNT 0x08000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_CACHE_MISS_TEXEL_COUNT 0x09000000 +#define VIVS_MC_PROFILE_CONFIG1_TX_RESET 0x0f000000 + +#define VIVS_MC_PROFILE_CONFIG2 0x00000478 +#define VIVS_MC_PROFILE_CONFIG2_MC__MASK 0x0000000f +#define VIVS_MC_PROFILE_CONFIG2_MC__SHIFT 0 +#define VIVS_MC_PROFILE_CONFIG2_MC_TOTAL_READ_REQ_8B_FROM_PIPELINE 0x00000001 +#define VIVS_MC_PROFILE_CONFIG2_MC_TOTAL_READ_REQ_8B_FROM_IP 0x00000002 +#define VIVS_MC_PROFILE_CONFIG2_MC_TOTAL_WRITE_REQ_8B_FROM_PIPELINE 0x00000003 +#define VIVS_MC_PROFILE_CONFIG2_MC_RESET 0x0000000f +#define VIVS_MC_PROFILE_CONFIG2_HI__MASK 0x00000f00 +#define VIVS_MC_PROFILE_CONFIG2_HI__SHIFT 8 +#define VIVS_MC_PROFILE_CONFIG2_HI_AXI_CYCLES_READ_REQUEST_STALLED 0x00000000 +#define VIVS_MC_PROFILE_CONFIG2_HI_AXI_CYCLES_WRITE_REQUEST_STALLED 0x00000100 +#define VIVS_MC_PROFILE_CONFIG2_HI_AXI_CYCLES_WRITE_DATA_STALLED 0x00000200 +#define VIVS_MC_PROFILE_CONFIG2_HI_RESET 0x00000f00 + +#define VIVS_MC_PROFILE_CONFIG3 0x0000047c + +#define VIVS_MC_BUS_CONFIG 0x00000480 +#define VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG__MASK 0x0000000f +#define VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG__SHIFT 0 +#define VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG(x) (((x) << VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG__SHIFT) & VIVS_MC_BUS_CONFIG_FE_BUS_CONFIG__MASK) +#define VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG__MASK 0x000000f0 +#define VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG__SHIFT 4 +#define VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG(x) (((x) << VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG__SHIFT) & VIVS_MC_BUS_CONFIG_TX_BUS_CONFIG__MASK) + +#define VIVS_MC_START_COMPOSITION 0x00000554 + +#define VIVS_MC_128B_MERGE 0x00000558 + + +#endif /* STATE_HI_XML */ diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed6..d19bd80 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -7,6 +7,12 @@ config DRM_I2C_ADV7511 help Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. +config DRM_I2C_ADIHDMI + tristate "ADI HDMI encoder" + default m if DRM_TILCDC + help + Support for ADI HDMI encoder. + config DRM_I2C_CH7006 tristate "Chrontel ch7006 TV encoder" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 2c72eb5..5a509ae 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -10,3 +10,6 @@ obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o tda998x-y := tda998x_drv.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o + +adihdmi-y := adihdmi_drv.o +obj-$(CONFIG_DRM_I2C_ADIHDMI) += adihdmi.o diff --git b/drivers/gpu/drm/i2c/adihdmi.h b/drivers/gpu/drm/i2c/adihdmi.h new file mode 100644 index 0000000..58d9a2b --- /dev/null +++ b/drivers/gpu/drm/i2c/adihdmi.h @@ -0,0 +1,289 @@ +/* + * Analog Devices ADIHDMI HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#ifndef __DRM_I2C_ADIHDMI_H__ +#define __DRM_I2C_ADIHDMI_H__ + +#include + +#define ADIHDMI_REG_CHIP_REVISION 0x00 +#define ADIHDMI_REG_N0 0x01 +#define ADIHDMI_REG_N1 0x02 +#define ADIHDMI_REG_N2 0x03 +#define ADIHDMI_REG_SPDIF_FREQ 0x04 +#define ADIHDMI_REG_CTS_AUTOMATIC1 0x05 +#define ADIHDMI_REG_CTS_AUTOMATIC2 0x06 +#define ADIHDMI_REG_CTS_MANUAL0 0x07 +#define ADIHDMI_REG_CTS_MANUAL1 0x08 +#define ADIHDMI_REG_CTS_MANUAL2 0x09 +#define ADIHDMI_REG_AUDIO_SOURCE 0x0a +#define ADIHDMI_REG_AUDIO_CONFIG 0x0b +#define ADIHDMI_REG_I2S_CONFIG 0x0c +#define ADIHDMI_REG_I2S_WIDTH 0x0d +#define ADIHDMI_REG_AUDIO_SUB_SRC0 0x0e +#define ADIHDMI_REG_AUDIO_SUB_SRC1 0x0f +#define ADIHDMI_REG_AUDIO_SUB_SRC2 0x10 +#define ADIHDMI_REG_AUDIO_SUB_SRC3 0x11 +#define ADIHDMI_REG_AUDIO_CFG1 0x12 +#define ADIHDMI_REG_AUDIO_CFG2 0x13 +#define ADIHDMI_REG_AUDIO_CFG3 0x14 +#define ADIHDMI_REG_I2C_FREQ_ID_CFG 0x15 +#define ADIHDMI_REG_VIDEO_INPUT_CFG1 0x16 +#define ADIHDMI_REG_CSC_UPPER(x) (0x18 + (x) * 2) +#define ADIHDMI_REG_CSC_LOWER(x) (0x19 + (x) * 2) +#define ADIHDMI_REG_SYNC_DECODER(x) (0x30 + (x)) +#define ADIHDMI_REG_DE_GENERATOR (0x35 + (x)) +#define ADIHDMI_REG_PIXEL_REPETITION 0x3b +#define ADIHDMI_REG_VIC_MANUAL 0x3c +#define ADIHDMI_REG_VIC_SEND 0x3d +#define ADIHDMI_REG_VIC_DETECTED 0x3e +#define ADIHDMI_REG_AUX_VIC_DETECTED 0x3f +#define ADIHDMI_REG_PACKET_ENABLE0 0x40 +#define ADIHDMI_REG_POWER 0x41 +#define ADIHDMI_REG_STATUS 0x42 +#define ADIHDMI_REG_EDID_I2C_ADDR 0x43 +#define ADIHDMI_REG_PACKET_ENABLE1 0x44 +#define ADIHDMI_REG_PACKET_I2C_ADDR 0x45 +#define ADIHDMI_REG_DSD_ENABLE 0x46 +#define ADIHDMI_REG_VIDEO_INPUT_CFG2 0x48 +#define ADIHDMI_REG_INFOFRAME_UPDATE 0x4a +#define ADIHDMI_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */ +#define ADIHDMI_REG_AVI_INFOFRAME_VERSION 0x52 +#define ADIHDMI_REG_AVI_INFOFRAME_LENGTH 0x53 +#define ADIHDMI_REG_AVI_INFOFRAME_CHECKSUM 0x54 +#define ADIHDMI_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */ +#define ADIHDMI_REG_AUDIO_INFOFRAME_VERSION 0x70 +#define ADIHDMI_REG_AUDIO_INFOFRAME_LENGTH 0x71 +#define ADIHDMI_REG_AUDIO_INFOFRAME_CHECKSUM 0x72 +#define ADIHDMI_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */ +#define ADIHDMI_REG_INT_ENABLE(x) (0x94 + (x)) +#define ADIHDMI_REG_INT(x) (0x96 + (x)) +#define ADIHDMI_REG_INPUT_CLK_DIV 0x9d +#define ADIHDMI_REG_PLL_STATUS 0x9e +#define ADIHDMI_REG_HDMI_POWER 0xa1 +#define ADIHDMI_REG_HDCP_HDMI_CFG 0xaf +#define ADIHDMI_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */ +#define ADIHDMI_REG_HDCP_STATUS 0xb8 +#define ADIHDMI_REG_BCAPS 0xbe +#define ADIHDMI_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */ +#define ADIHDMI_REG_EDID_SEGMENT 0xc4 +#define ADIHDMI_REG_DDC_STATUS 0xc8 +#define ADIHDMI_REG_EDID_READ_CTRL 0xc9 +#define ADIHDMI_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */ +#define ADIHDMI_REG_TIMING_GEN_SEQ 0xd0 +#define ADIHDMI_REG_POWER2 0xd6 +#define ADIHDMI_REG_HSYNC_PLACEMENT_MSB 0xfa + +#define ADIHDMI_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */ +#define ADIHDMI_REG_TMDS_CLOCK_INV 0xde +#define ADIHDMI_REG_ARC_CTRL 0xdf +#define ADIHDMI_REG_CEC_I2C_ADDR 0xe1 +#define ADIHDMI_REG_CEC_CTRL 0xe2 +#define ADIHDMI_REG_CHIP_ID_HIGH 0xf5 +#define ADIHDMI_REG_CHIP_ID_LOW 0xf6 + +#define ADIHDMI_CSC_ENABLE BIT(7) +#define ADIHDMI_CSC_UPDATE_MODE BIT(5) + +#define ADIHDMI_INT0_HDP BIT(7) +#define ADIHDMI_INT0_VSYNC BIT(5) +#define ADIHDMI_INT0_AUDIO_FIFO_FULL BIT(4) +#define ADIHDMI_INT0_EDID_READY BIT(2) +#define ADIHDMI_INT0_HDCP_AUTHENTICATED BIT(1) + +#define ADIHDMI_INT1_DDC_ERROR BIT(7) +#define ADIHDMI_INT1_BKSV BIT(6) +#define ADIHDMI_INT1_CEC_TX_READY BIT(5) +#define ADIHDMI_INT1_CEC_TX_ARBIT_LOST BIT(4) +#define ADIHDMI_INT1_CEC_TX_RETRY_TIMEOUT BIT(3) +#define ADIHDMI_INT1_CEC_RX_READY3 BIT(2) +#define ADIHDMI_INT1_CEC_RX_READY2 BIT(1) +#define ADIHDMI_INT1_CEC_RX_READY1 BIT(0) + +#define ADIHDMI_ARC_CTRL_POWER_DOWN BIT(0) + +#define ADIHDMI_CEC_CTRL_POWER_DOWN BIT(0) + +#define ADIHDMI_POWER_POWER_DOWN BIT(6) + +#define ADIHDMI_HDMI_CFG_MODE_MASK 0x2 +#define ADIHDMI_HDMI_CFG_MODE_DVI 0x0 +#define ADIHDMI_HDMI_CFG_MODE_HDMI 0x2 + +#define ADIHDMI_AUDIO_SELECT_I2C 0x0 +#define ADIHDMI_AUDIO_SELECT_SPDIF 0x1 +#define ADIHDMI_AUDIO_SELECT_DSD 0x2 +#define ADIHDMI_AUDIO_SELECT_HBR 0x3 +#define ADIHDMI_AUDIO_SELECT_DST 0x4 + +#define ADIHDMI_I2S_SAMPLE_LEN_16 0x2 +#define ADIHDMI_I2S_SAMPLE_LEN_20 0x3 +#define ADIHDMI_I2S_SAMPLE_LEN_18 0x4 +#define ADIHDMI_I2S_SAMPLE_LEN_22 0x5 +#define ADIHDMI_I2S_SAMPLE_LEN_19 0x8 +#define ADIHDMI_I2S_SAMPLE_LEN_23 0x9 +#define ADIHDMI_I2S_SAMPLE_LEN_24 0xb +#define ADIHDMI_I2S_SAMPLE_LEN_17 0xc +#define ADIHDMI_I2S_SAMPLE_LEN_21 0xd + +#define ADIHDMI_SAMPLE_FREQ_44100 0x0 +#define ADIHDMI_SAMPLE_FREQ_48000 0x2 +#define ADIHDMI_SAMPLE_FREQ_32000 0x3 +#define ADIHDMI_SAMPLE_FREQ_88200 0x8 +#define ADIHDMI_SAMPLE_FREQ_96000 0xa +#define ADIHDMI_SAMPLE_FREQ_176400 0xc +#define ADIHDMI_SAMPLE_FREQ_192000 0xe + +#define ADIHDMI_STATUS_POWER_DOWN_POLARITY BIT(7) +#define ADIHDMI_STATUS_HPD BIT(6) +#define ADIHDMI_STATUS_MONITOR_SENSE BIT(5) +#define ADIHDMI_STATUS_I2S_32BIT_MODE BIT(3) + +#define ADIHDMI_PACKET_ENABLE_N_CTS BIT(8+6) +#define ADIHDMI_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5) +#define ADIHDMI_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4) +#define ADIHDMI_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3) +#define ADIHDMI_PACKET_ENABLE_GC BIT(7) +#define ADIHDMI_PACKET_ENABLE_SPD BIT(6) +#define ADIHDMI_PACKET_ENABLE_MPEG BIT(5) +#define ADIHDMI_PACKET_ENABLE_ACP BIT(4) +#define ADIHDMI_PACKET_ENABLE_ISRC BIT(3) +#define ADIHDMI_PACKET_ENABLE_GM BIT(2) +#define ADIHDMI_PACKET_ENABLE_SPARE2 BIT(1) +#define ADIHDMI_PACKET_ENABLE_SPARE1 BIT(0) + +#define ADIHDMI_REG_POWER2_HDP_SRC_MASK 0xc0 +#define ADIHDMI_REG_POWER2_HDP_SRC_BOTH 0x00 +#define ADIHDMI_REG_POWER2_HDP_SRC_HDP 0x40 +#define ADIHDMI_REG_POWER2_HDP_SRC_CEC 0x80 +#define ADIHDMI_REG_POWER2_HDP_SRC_NONE 0xc0 +#define ADIHDMI_REG_POWER2_TDMS_ENABLE BIT(4) +#define ADIHDMI_REG_POWER2_GATE_INPUT_CLK BIT(0) + +#define ADIHDMI_LOW_REFRESH_RATE_NONE 0x0 +#define ADIHDMI_LOW_REFRESH_RATE_24HZ 0x1 +#define ADIHDMI_LOW_REFRESH_RATE_25HZ 0x2 +#define ADIHDMI_LOW_REFRESH_RATE_30HZ 0x3 + +#define ADIHDMI_AUDIO_CFG3_LEN_MASK 0x0f +#define ADIHDMI_I2C_FREQ_ID_CFG_RATE_MASK 0xf0 + +#define ADIHDMI_AUDIO_SOURCE_I2S 0 +#define ADIHDMI_AUDIO_SOURCE_SPDIF 1 + +#define ADIHDMI_I2S_FORMAT_I2S 0 +#define ADIHDMI_I2S_FORMAT_RIGHT_J 1 +#define ADIHDMI_I2S_FORMAT_LEFT_J 2 + +#define ADIHDMI_PACKET(p, x) ((p) * 0x20 + (x)) +#define ADIHDMI_PACKET_SDP(x) ADIHDMI_PACKET(0, x) +#define ADIHDMI_PACKET_MPEG(x) ADIHDMI_PACKET(1, x) +#define ADIHDMI_PACKET_ACP(x) ADIHDMI_PACKET(2, x) +#define ADIHDMI_PACKET_ISRC1(x) ADIHDMI_PACKET(3, x) +#define ADIHDMI_PACKET_ISRC2(x) ADIHDMI_PACKET(4, x) +#define ADIHDMI_PACKET_GM(x) ADIHDMI_PACKET(5, x) +#define ADIHDMI_PACKET_SPARE(x) ADIHDMI_PACKET(6, x) + +enum adihdmi_input_clock { + ADIHDMI_INPUT_CLOCK_1X, + ADIHDMI_INPUT_CLOCK_2X, + ADIHDMI_INPUT_CLOCK_DDR, +}; + +enum adihdmi_input_justification { + ADIHDMI_INPUT_JUSTIFICATION_EVENLY = 0, + ADIHDMI_INPUT_JUSTIFICATION_RIGHT = 1, + ADIHDMI_INPUT_JUSTIFICATION_LEFT = 2, +}; + +enum adihdmi_input_sync_pulse { + ADIHDMI_INPUT_SYNC_PULSE_DE = 0, + ADIHDMI_INPUT_SYNC_PULSE_HSYNC = 1, + ADIHDMI_INPUT_SYNC_PULSE_VSYNC = 2, + ADIHDMI_INPUT_SYNC_PULSE_NONE = 3, +}; + +/** + * enum adihdmi_sync_polarity - Polarity for the input sync signals + * @ADIHDMI_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of + * the currently configured mode. + * @ADIHDMI_SYNC_POLARITY_LOW: Sync polarity is low + * @ADIHDMI_SYNC_POLARITY_HIGH: Sync polarity is high + * + * If the polarity is set to either LOW or HIGH the driver will configure the + * ADIHDMI to internally invert the sync signal if required to match the sync + * polarity setting for the currently selected output mode. + * + * If the polarity is set to PASSTHROUGH, the ADIHDMI will route the signal + * unchanged. This is used when the upstream graphics core already generates + * the sync signals with the correct polarity. + */ +enum adihdmi_sync_polarity { + ADIHDMI_SYNC_POLARITY_PASSTHROUGH, + ADIHDMI_SYNC_POLARITY_LOW, + ADIHDMI_SYNC_POLARITY_HIGH, +}; + +/** + * struct adihdmi_link_config - Describes adihdmi hardware configuration + * @input_color_depth: Number of bits per color component (8, 10 or 12) + * @input_colorspace: The input colorspace (RGB, YUV444, YUV422) + * @input_clock: The input video clock style (1x, 2x, DDR) + * @input_style: The input component arrangement variant + * @input_justification: Video input format bit justification + * @clock_delay: Clock delay for the input clock (in ps) + * @embedded_sync: Video input uses BT.656-style embedded sync + * @sync_pulse: Select the sync pulse + * @vsync_polarity: vsync input signal configuration + * @hsync_polarity: hsync input signal configuration + */ +struct adihdmi_link_config { + unsigned int input_color_depth; + enum hdmi_colorspace input_colorspace; + enum adihdmi_input_clock input_clock; + unsigned int input_style; + enum adihdmi_input_justification input_justification; + + int clock_delay; + + bool embedded_sync; + enum adihdmi_input_sync_pulse sync_pulse; + enum adihdmi_sync_polarity vsync_polarity; + enum adihdmi_sync_polarity hsync_polarity; +}; + +/** + * enum adihdmi_csc_scaling - Scaling factor for the ADIHDMI CSC + * @ADIHDMI_CSC_SCALING_1: CSC results are not scaled + * @ADIHDMI_CSC_SCALING_2: CSC results are scaled by a factor of two + * @ADIHDMI_CSC_SCALING_4: CSC results are scalled by a factor of four + */ +enum adihdmi_csc_scaling { + ADIHDMI_CSC_SCALING_1 = 0, + ADIHDMI_CSC_SCALING_2 = 1, + ADIHDMI_CSC_SCALING_4 = 2, +}; + +/** + * struct adihdmi_video_config - Describes adihdmi hardware configuration + * @csc_enable: Whether to enable color space conversion + * @csc_scaling_factor: Color space conversion scaling factor + * @csc_coefficents: Color space conversion coefficents + * @hdmi_mode: Whether to use HDMI or DVI output mode + * @avi_infoframe: HDMI infoframe + */ +struct adihdmi_video_config { + bool csc_enable; + enum adihdmi_csc_scaling csc_scaling_factor; + const uint16_t *csc_coefficents; + + bool hdmi_mode; + struct hdmi_avi_infoframe avi_infoframe; +}; + +#endif /* __DRM_I2C_ADIHDMI_H__ */ diff --git b/drivers/gpu/drm/i2c/adihdmi_drv.c b/drivers/gpu/drm/i2c/adihdmi_drv.c new file mode 100644 index 0000000..2b9106e --- /dev/null +++ b/drivers/gpu/drm/i2c/adihdmi_drv.c @@ -0,0 +1,1282 @@ +/* + * Analog Devices ADIHDMI HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * Copyright 2015 Konsulko Group + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "adihdmi.h" + +#define ADIHDMI_INFOFRAME_PACKETS (0x7900) + +struct adihdmi { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + + struct regmap *regmap; + struct regmap *packet_memory_regmap; + enum drm_connector_status status; + bool powered; + + unsigned int f_tmds; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + + wait_queue_head_t wq; + struct drm_encoder *encoder; + + bool embedded_sync; + enum adihdmi_sync_polarity vsync_polarity; + enum adihdmi_sync_polarity hsync_polarity; + bool rgb; + + struct edid *edid; + + struct gpio_desc *gpio_pd; +}; + +struct adihdmi2 { + struct adihdmi base; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +/* ADI recommended values for proper operation. */ +static const struct reg_sequence adihdmi_fixed_registers[] = { + { 0x98, 0x03 }, + { 0x9a, 0xe0 }, + { 0x9c, 0x30 }, + { 0x9d, 0x61 }, + { 0xa2, 0xa4 }, + { 0xa3, 0xa4 }, + { 0xe0, 0xd0 }, + { 0xf9, 0x00 }, + { 0x55, 0x02 }, +}; + +/* ----------------------------------------------------------------------------- + * Register access + */ + +static const uint8_t adihdmi_register_defaults[] = { + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */ + 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13, + 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ + 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84, + 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */ + 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0, + 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */ + 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */ + 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */ + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */ + 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04, + 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, + 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */ + 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static bool adihdmi_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADIHDMI_REG_CHIP_REVISION: + case ADIHDMI_REG_SPDIF_FREQ: + case ADIHDMI_REG_CTS_AUTOMATIC1: + case ADIHDMI_REG_CTS_AUTOMATIC2: + case ADIHDMI_REG_VIC_DETECTED: + case ADIHDMI_REG_VIC_SEND: + case ADIHDMI_REG_AUX_VIC_DETECTED: + case ADIHDMI_REG_STATUS: + case ADIHDMI_REG_GC(1): + case ADIHDMI_REG_INT(0): + case ADIHDMI_REG_INT(1): + case ADIHDMI_REG_PLL_STATUS: + case ADIHDMI_REG_AN(0): + case ADIHDMI_REG_AN(1): + case ADIHDMI_REG_AN(2): + case ADIHDMI_REG_AN(3): + case ADIHDMI_REG_AN(4): + case ADIHDMI_REG_AN(5): + case ADIHDMI_REG_AN(6): + case ADIHDMI_REG_AN(7): + case ADIHDMI_REG_HDCP_STATUS: + case ADIHDMI_REG_BCAPS: + case ADIHDMI_REG_BKSV(0): + case ADIHDMI_REG_BKSV(1): + case ADIHDMI_REG_BKSV(2): + case ADIHDMI_REG_BKSV(3): + case ADIHDMI_REG_BKSV(4): + case ADIHDMI_REG_DDC_STATUS: + case ADIHDMI_REG_BSTATUS(0): + case ADIHDMI_REG_BSTATUS(1): + case ADIHDMI_REG_CHIP_ID_HIGH: + case ADIHDMI_REG_CHIP_ID_LOW: + return true; + } + + return false; +} + +static const struct regmap_config adihdmi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, + .reg_defaults_raw = adihdmi_register_defaults, + .num_reg_defaults_raw = ARRAY_SIZE(adihdmi_register_defaults), + + .volatile_reg = adihdmi_register_volatile, +}; + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + + static void adihdmi_audio_setup(struct adihdmi * adihdmi) +{ + /* Select I2S. */ + regmap_write(adihdmi->regmap, ADIHDMI_REG_AUDIO_SOURCE, 0x01); + regmap_write(adihdmi->regmap, ADIHDMI_REG_I2S_CONFIG, 0x84); + + /* Setup clocks for 48KHz. */ + regmap_write(adihdmi->regmap, ADIHDMI_REG_N0, 0x00); + regmap_write(adihdmi->regmap, ADIHDMI_REG_N1, 0x18); + regmap_write(adihdmi->regmap, ADIHDMI_REG_N2, 0x00); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_I2C_FREQ_ID_CFG, 0xF0, 0x20); + + /* Set audio word length to 24 bits. */ + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_AUDIO_CFG3, 0x0F, 0x0B); + + /* Update audio infoframe. */ + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_INFOFRAME_UPDATE, 0x20, 0x20); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_AUDIO_INFOFRAME(0), 0x07, 0x01); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_AUDIO_INFOFRAME(3), 0x1F, 0x00); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_INFOFRAME_UPDATE, 0x20, 0x00); +} + +static void adihdmi_set_colormap(struct adihdmi *adihdmi, bool enable, + const uint16_t *coeff, + unsigned int scaling_factor) +{ + unsigned int i; + + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_CSC_UPPER(1), + ADIHDMI_CSC_UPDATE_MODE, ADIHDMI_CSC_UPDATE_MODE); + + if (enable) { + for (i = 0; i < 12; ++i) { + regmap_update_bits(adihdmi->regmap, + ADIHDMI_REG_CSC_UPPER(i), + 0x1f, coeff[i] >> 8); + regmap_write(adihdmi->regmap, + ADIHDMI_REG_CSC_LOWER(i), + coeff[i] & 0xff); + } + } + + if (enable) + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_CSC_UPPER(0), + 0xe0, 0x80 | (scaling_factor << 5)); + else + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_CSC_UPPER(0), + 0x80, 0x00); + + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_CSC_UPPER(1), + ADIHDMI_CSC_UPDATE_MODE, 0); +} + +static int adihdmi_packet_enable(struct adihdmi *adihdmi, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_PACKET_ENABLE0, + packet, 0xff); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_PACKET_ENABLE1, + packet, 0xff); + } + + return 0; +} + +static int adihdmi_packet_disable(struct adihdmi *adihdmi, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_PACKET_ENABLE0, + packet, 0x00); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_PACKET_ENABLE1, + packet, 0x00); + } + + return 0; +} + +/* Coefficients for adihdmi color space conversion */ +static const uint16_t adihdmi_csc_ycbcr_to_rgb[] = { + 0x0734, 0x04ad, 0x0000, 0x1c1b, + 0x1ddc, 0x04ad, 0x1f24, 0x0135, + 0x0000, 0x04ad, 0x087c, 0x1b77, +}; + +static void adihdmi_set_config_csc(struct adihdmi *adihdmi, + struct drm_connector *connector, + bool rgb) +{ + struct adihdmi_video_config config; + bool output_format_422, output_format_ycbcr; + unsigned int mode; + uint8_t infoframe[17]; + + if (adihdmi->edid) + config.hdmi_mode = drm_detect_hdmi_monitor(adihdmi->edid); + else + config.hdmi_mode = false; + + hdmi_avi_infoframe_init(&config.avi_infoframe); + + config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN; + + if (rgb) { + config.csc_enable = false; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } else { + config.csc_scaling_factor = ADIHDMI_CSC_SCALING_4; + config.csc_coefficents = adihdmi_csc_ycbcr_to_rgb; + + if ((connector->display_info.color_formats & + DRM_COLOR_FORMAT_YCRCB422) && + config.hdmi_mode) { + config.csc_enable = false; + config.avi_infoframe.colorspace = + HDMI_COLORSPACE_YUV422; + } else { + config.csc_enable = true; + config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB; + } + } + + if (config.hdmi_mode) { + mode = ADIHDMI_HDMI_CFG_MODE_HDMI; + + switch (config.avi_infoframe.colorspace) { + case HDMI_COLORSPACE_YUV444: + output_format_422 = false; + output_format_ycbcr = true; + break; + case HDMI_COLORSPACE_YUV422: + output_format_422 = true; + output_format_ycbcr = true; + break; + default: + output_format_422 = false; + output_format_ycbcr = false; + break; + } + } else { + mode = ADIHDMI_HDMI_CFG_MODE_DVI; + output_format_422 = false; + output_format_ycbcr = false; + } + + adihdmi_packet_disable(adihdmi, ADIHDMI_INFOFRAME_PACKETS); + + adihdmi_set_colormap(adihdmi, config.csc_enable, + config.csc_coefficents, + config.csc_scaling_factor); + + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_VIDEO_INPUT_CFG1, 0x81, + (output_format_422 << 7) | output_format_ycbcr); + + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_HDCP_HDMI_CFG, + ADIHDMI_HDMI_CFG_MODE_MASK, mode); + + hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe, + sizeof(infoframe)); + + /* The AVI infoframe id is not configurable */ + regmap_bulk_write(adihdmi->regmap, ADIHDMI_REG_AVI_INFOFRAME_VERSION, + infoframe + 1, sizeof(infoframe) - 1); + + adihdmi_packet_enable(adihdmi, ADIHDMI_INFOFRAME_PACKETS); +} + +static void adihdmi_set_link_config(struct adihdmi *adihdmi, + const struct adihdmi_link_config *config) +{ + /* + * The input style values documented in the datasheet don't match the + * hardware register field values :-( + */ + static const unsigned int input_styles[4] = { 0, 2, 1, 3 }; + + unsigned int clock_delay; + unsigned int color_depth; + unsigned int input_id; + + clock_delay = (config->clock_delay + 1200) / 400; + color_depth = config->input_color_depth == 8 ? 3 + : (config->input_color_depth == 10 ? 1 : 2); + + /* TODO Support input ID 6 */ + if (config->input_colorspace != HDMI_COLORSPACE_YUV422) + input_id = config->input_clock == ADIHDMI_INPUT_CLOCK_DDR + ? 5 : 0; + else if (config->input_clock == ADIHDMI_INPUT_CLOCK_DDR) + input_id = config->embedded_sync ? 8 : 7; + else if (config->input_clock == ADIHDMI_INPUT_CLOCK_2X) + input_id = config->embedded_sync ? 4 : 3; + else + input_id = config->embedded_sync ? 2 : 1; + + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_I2C_FREQ_ID_CFG, 0xf, + input_id); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_VIDEO_INPUT_CFG1, 0x7e, + (color_depth << 4) | + (input_styles[config->input_style] << 2)); + regmap_write(adihdmi->regmap, ADIHDMI_REG_VIDEO_INPUT_CFG2, + config->input_justification << 3); + regmap_write(adihdmi->regmap, ADIHDMI_REG_TIMING_GEN_SEQ, + config->sync_pulse << 2); + + regmap_write(adihdmi->regmap, 0xba, clock_delay << 5); + + adihdmi->embedded_sync = config->embedded_sync; + adihdmi->hsync_polarity = config->hsync_polarity; + adihdmi->vsync_polarity = config->vsync_polarity; + adihdmi->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; +} + +static void adihdmi_power_on(struct adihdmi *adihdmi) +{ + adihdmi->current_edid_segment = -1; + + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(0), + ADIHDMI_INT0_EDID_READY); + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(1), + ADIHDMI_INT1_DDC_ERROR); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER, + ADIHDMI_POWER_POWER_DOWN, 0); + + /* + * Per spec it is allowed to pulse the HDP signal to indicate that the + * EDID information has changed. Some monitors do this when they wakeup + * from standby or are enabled. When the HDP goes low the adihdmi is + * reset and the outputs are disabled which might cause the monitor to + * go to standby again. To avoid this we ignore the HDP pin for the + * first few seconds after enabling the output. + */ + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER2, + ADIHDMI_REG_POWER2_HDP_SRC_MASK, + ADIHDMI_REG_POWER2_HDP_SRC_NONE); + + /* + * Most of the registers are reset during power down or when HPD is low. + */ + regcache_sync(adihdmi->regmap); + + adihdmi->powered = true; +} + +static void adihdmi_power_off(struct adihdmi *adihdmi) +{ + /* TODO: setup additional power down modes */ + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER, + ADIHDMI_POWER_POWER_DOWN, + ADIHDMI_POWER_POWER_DOWN); + regcache_mark_dirty(adihdmi->regmap); + + adihdmi->powered = false; +} + +/* ----------------------------------------------------------------------------- + * Interrupt and hotplug detection + */ + +static bool adihdmi_hpd(struct adihdmi *adihdmi) +{ + unsigned int irq0; + int ret; + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_INT(0), &irq0); + if (ret < 0) + return false; + + if (irq0 & ADIHDMI_INT0_HDP) { + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(0), + ADIHDMI_INT0_HDP); + return true; + } + + return false; +} + +static int adihdmi_irq_process(struct adihdmi *adihdmi) +{ + unsigned int irq0, irq1; + int ret; + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_INT(0), &irq0); + if (ret < 0) + return ret; + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_INT(1), &irq1); + if (ret < 0) + return ret; + + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(0), irq0); + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(1), irq1); + + if (irq0 & ADIHDMI_INT0_HDP) + drm_helper_hpd_irq_event(adihdmi->encoder->dev); + + if (irq0 & ADIHDMI_INT0_EDID_READY || irq1 & ADIHDMI_INT1_DDC_ERROR) { + adihdmi->edid_read = true; + + if (adihdmi->i2c_main->irq) + wake_up_all(&adihdmi->wq); + } + + return 0; +} + +static irqreturn_t adihdmi_irq_handler(int irq, void *devid) +{ + struct adihdmi *adihdmi = devid; + int ret; + + ret = adihdmi_irq_process(adihdmi); + return ret < 0 ? IRQ_NONE : IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * EDID retrieval + */ + +static int adihdmi_wait_for_edid(struct adihdmi *adihdmi, int timeout) +{ + int ret; + + if (adihdmi->i2c_main->irq) { + ret = wait_event_interruptible_timeout(adihdmi->wq, + adihdmi->edid_read, msecs_to_jiffies(timeout)); + } else { + for (; timeout > 0; timeout -= 25) { + ret = adihdmi_irq_process(adihdmi); + if (ret < 0) + break; + + if (adihdmi->edid_read) + break; + + msleep(25); + } + } + + return adihdmi->edid_read ? 0 : -EIO; +} + +static int adihdmi_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct adihdmi *adihdmi = data; + struct i2c_msg xfer[2]; + uint8_t offset; + unsigned int i; + int ret; + + if (len > 128) + return -EINVAL; + + if (adihdmi->current_edid_segment != block / 2) { + unsigned int status; + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_DDC_STATUS, + &status); + if (ret < 0) + return ret; + + if (status != 2) { + adihdmi->edid_read = false; + regmap_write(adihdmi->regmap, ADIHDMI_REG_EDID_SEGMENT, + block); + ret = adihdmi_wait_for_edid(adihdmi, 200); + if (ret < 0) + return ret; + } + + /* Break this apart, hopefully more I2C controllers will + * support 64 byte transfers than 256 byte transfers + */ + + xfer[0].addr = adihdmi->i2c_edid->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = &offset; + xfer[1].addr = adihdmi->i2c_edid->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 64; + xfer[1].buf = adihdmi->edid_buf; + + offset = 0; + + for (i = 0; i < 4; ++i) { + ret = i2c_transfer(adihdmi->i2c_edid->adapter, xfer, + ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + xfer[1].buf += 64; + offset += 64; + } + + adihdmi->current_edid_segment = block / 2; + } + + if (block % 2 == 0) + memcpy(buf, adihdmi->edid_buf, len); + else + memcpy(buf, adihdmi->edid_buf + 128, len); + + return 0; +} + +static int adihdmi_mode_valid(struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +/* ----------------------------------------------------------------------------- + * DT and private structure operations + */ + +#define conn_to_adihdmi2(x) \ + container_of(x, struct adihdmi2, connector); + +#define enc_to_adihdmi2(x) \ + container_of(x, struct adihdmi2, encoder); + +#define enc_to_adihdmi(x) \ + (&(container_of(x, struct adihdmi2, encoder)->base)) + +static int adihdmi_parse_dt(struct device_node *np, + struct adihdmi_link_config *config) +{ + memset(config, 0, sizeof(*config)); + + config->input_color_depth = 8; + + config->input_colorspace = HDMI_COLORSPACE_RGB; + //config->input_colorspace = HDMI_COLORSPACE_YUV422; + //config->input_colorspace = HDMI_COLORSPACE_YUV444; + + config->input_clock = ADIHDMI_INPUT_CLOCK_1X; + //config->input_clock = ADIHDMI_INPUT_CLOCK_2X; + //config->input_clock = ADIHDMI_INPUT_CLOCK_DDR; + + if (config->input_colorspace == HDMI_COLORSPACE_YUV422 || + config->input_clock != ADIHDMI_INPUT_CLOCK_1X) { + + config->input_style = 1; + //config->input_justification = ADIHDMI_INPUT_JUSTIFICATION_LEFT; + config->input_justification = ADIHDMI_INPUT_JUSTIFICATION_EVENLY; + //config->input_justification = ADIHDMI_INPUT_JUSTIFICATION_RIGHT; + + } else { + config->input_style = 1; + config->input_justification = ADIHDMI_INPUT_JUSTIFICATION_LEFT; + } + + config->clock_delay = 0; + config->embedded_sync = 0; + + /* Hardcode the sync pulse configurations for now. */ + config->sync_pulse = ADIHDMI_INPUT_SYNC_PULSE_NONE; + config->vsync_polarity = ADIHDMI_SYNC_POLARITY_PASSTHROUGH; + config->hsync_polarity = ADIHDMI_SYNC_POLARITY_PASSTHROUGH; + + return 0; +} + +static const int edid_i2c_addr = 0x7e; +static const int packet_i2c_addr = 0x70; +static const int cec_i2c_addr = 0x78; + +static int adihdmi_create(struct i2c_client *i2c, struct adihdmi *adihdmi) +{ + struct adihdmi_link_config link_config; + struct device *dev = &i2c->dev; + unsigned int val; + int ret; + + adihdmi->powered = false; + adihdmi->status = connector_status_disconnected; + + ret = adihdmi_parse_dt(NULL, &link_config); + if (ret) + { + pr_err("%s - %d - Bad parse\n", __FUNCTION__, __LINE__); + return -EINVAL; + } + + /* + * The power down GPIO is optional. If present, toggle it from active to + * inactive to wake up the encoder. + */ + adihdmi->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(adihdmi->gpio_pd)) + { + pr_err("%s - %d - Bad PD GPIO\n", __FUNCTION__, __LINE__); + return PTR_ERR(adihdmi->gpio_pd); + } + + if (adihdmi->gpio_pd) { + mdelay(5); + gpiod_set_value_cansleep(adihdmi->gpio_pd, 0); + } + + adihdmi->regmap = devm_regmap_init_i2c(i2c, &adihdmi_regmap_config); + if (IS_ERR(adihdmi->regmap)) + { + pr_err("%s - %d - Bad reg map init\n", __FUNCTION__, __LINE__); + return PTR_ERR(adihdmi->regmap); + } + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_CHIP_REVISION, &val); + if (ret) + { + pr_err("%s - %d - Bad reg map read\n", __FUNCTION__, __LINE__); + return ret; + } + dev_dbg(dev, "Rev. %d\n", val); + + ret = regmap_register_patch(adihdmi->regmap, adihdmi_fixed_registers, + ARRAY_SIZE(adihdmi_fixed_registers)); + if (ret) + { + pr_err("%s - %d - Bad reg map patch\n", __FUNCTION__, __LINE__); + return ret; + } + + regmap_write(adihdmi->regmap, ADIHDMI_REG_EDID_I2C_ADDR, edid_i2c_addr); + regmap_write(adihdmi->regmap, ADIHDMI_REG_PACKET_I2C_ADDR, + packet_i2c_addr); + regmap_write(adihdmi->regmap, ADIHDMI_REG_CEC_I2C_ADDR, cec_i2c_addr); + adihdmi_packet_disable(adihdmi, 0xffff); + + adihdmi->i2c_main = i2c; + adihdmi->i2c_edid = i2c_new_dummy(i2c->adapter, edid_i2c_addr >> 1); + if (!adihdmi->i2c_edid) + { + pr_err("%s - %d - No mem for EDID\n", __FUNCTION__, __LINE__); + return -ENOMEM; + } + + if (i2c->irq) { + init_waitqueue_head(&adihdmi->wq); + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, + adihdmi_irq_handler, + IRQF_ONESHOT, dev_name(dev), + adihdmi); + if (ret) + { + pr_err("%s - %d - Bad IRQ thread request\n", __FUNCTION__, __LINE__); + goto err_i2c_unregister_device; + } + } + + /* CEC is unused for now */ + regmap_write(adihdmi->regmap, ADIHDMI_REG_CEC_CTRL, + ADIHDMI_CEC_CTRL_POWER_DOWN); + + adihdmi_power_off(adihdmi); + + adihdmi_set_link_config(adihdmi, &link_config); + + adihdmi_audio_setup(adihdmi); + + return 0; + +err_i2c_unregister_device: + i2c_unregister_device(adihdmi->i2c_edid); + + return ret; +} + +static void adihdmi_destroy(struct adihdmi *priv) +{ + i2c_unregister_device(priv->i2c_edid); +} + +/* ----------------------------------------------------------------------------- + * Encoder operations + */ + +static int adihdmi_encoder_get_modes(struct adihdmi *adihdmi, + struct drm_connector *connector) +{ + struct edid *edid; + unsigned int count; + + /* Reading the EDID only works if the device is powered */ + if (!adihdmi->powered) { + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(0), + ADIHDMI_INT0_EDID_READY); + regmap_write(adihdmi->regmap, ADIHDMI_REG_INT(1), + ADIHDMI_INT1_DDC_ERROR); + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER, + ADIHDMI_POWER_POWER_DOWN, 0); + adihdmi->current_edid_segment = -1; + } + + edid = drm_do_get_edid(connector, adihdmi_get_edid_block, adihdmi); + + if (!adihdmi->powered) + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER, + ADIHDMI_POWER_POWER_DOWN, + ADIHDMI_POWER_POWER_DOWN); + + kfree(adihdmi->edid); + adihdmi->edid = edid; + if (!edid) + { + pr_err("%s - %d - No EDID\n", __FUNCTION__, __LINE__); + return 0; + } + + drm_mode_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + + adihdmi_set_config_csc(adihdmi, connector, adihdmi->rgb); + + return count; +} + +static void adihdmi_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct adihdmi2 *priv2 = enc_to_adihdmi2(encoder); + + if (mode == DRM_MODE_DPMS_ON) + adihdmi_power_on(&priv2->base); + else + adihdmi_power_off(&priv2->base); +} + + static enum drm_connector_status +adihdmi_encoder_detect(struct adihdmi *adihdmi, + struct drm_connector *connector) +{ + enum drm_connector_status status; + unsigned int val; + bool hpd; + int ret; + + ret = regmap_read(adihdmi->regmap, ADIHDMI_REG_STATUS, &val); + if (ret < 0) + { + pr_err("%s - %d - Disconnected\n", __FUNCTION__, __LINE__); + return connector_status_disconnected; + } + + if (val & ADIHDMI_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + hpd = adihdmi_hpd(adihdmi); + + /* The chip resets itself when the cable is disconnected, so in case + * there is a pending HPD interrupt and the cable is connected there was + * at least one transition from disconnected to connected and the chip + * has to be reinitialized. */ + if (status == connector_status_connected && hpd && adihdmi->powered) { + regcache_mark_dirty(adihdmi->regmap); + adihdmi_power_on(adihdmi); + adihdmi_encoder_get_modes(adihdmi, connector); + if (adihdmi->status == connector_status_connected) + status = connector_status_disconnected; + } else { + /* Renable HDP sensing */ + regmap_update_bits(adihdmi->regmap, ADIHDMI_REG_POWER2, + ADIHDMI_REG_POWER2_HDP_SRC_MASK, + ADIHDMI_REG_POWER2_HDP_SRC_BOTH); + } + + adihdmi->status = status; + return status; +} + +static bool adihdmi_encoder_mode_fixup(struct drm_encoder *encoder, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int adihdmi_encoder_mode_valid(struct drm_encoder *encoder, struct drm_display_mode *mode) +{ + return adihdmi_mode_valid(mode); +} + +static void adihdmi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + unsigned int low_refresh_rate; + unsigned int hsync_polarity = 0; + unsigned int vsync_polarity = 0; + struct adihdmi *adihdmi = enc_to_adihdmi(encoder); + + if (adihdmi->embedded_sync) { + unsigned int hsync_offset, hsync_len; + unsigned int vsync_offset, vsync_len; + + hsync_offset = adj_mode->crtc_hsync_start - + adj_mode->crtc_hdisplay; + vsync_offset = adj_mode->crtc_vsync_start - + adj_mode->crtc_vdisplay; + hsync_len = adj_mode->crtc_hsync_end - + adj_mode->crtc_hsync_start; + vsync_len = adj_mode->crtc_vsync_end - + adj_mode->crtc_vsync_start; + + /* The hardware vsync generator has a off-by-one bug */ + vsync_offset += 1; + + regmap_write(adihdmi->regmap, ADIHDMI_REG_HSYNC_PLACEMENT_MSB, + ((hsync_offset >> 10) & 0x7) << 5); + regmap_write(adihdmi->regmap, ADIHDMI_REG_SYNC_DECODER(0), + (hsync_offset >> 2) & 0xff); + regmap_write(adihdmi->regmap, ADIHDMI_REG_SYNC_DECODER(1), + ((hsync_offset & 0x3) << 6) | + ((hsync_len >> 4) & 0x3f)); + regmap_write(adihdmi->regmap, ADIHDMI_REG_SYNC_DECODER(2), + ((hsync_len & 0xf) << 4) | + ((vsync_offset >> 6) & 0xf)); + regmap_write(adihdmi->regmap, ADIHDMI_REG_SYNC_DECODER(3), + ((vsync_offset & 0x3f) << 2) | + ((vsync_len >> 8) & 0x3)); + regmap_write(adihdmi->regmap, ADIHDMI_REG_SYNC_DECODER(4), + vsync_len & 0xff); + + hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC); + vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC); + } else { + enum adihdmi_sync_polarity mode_hsync_polarity; + enum adihdmi_sync_polarity mode_vsync_polarity; + + /** + * If the input signal is always low or always high we want to + * invert or let it passthrough depending on the polarity of the + * current mode. + **/ + if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_hsync_polarity = ADIHDMI_SYNC_POLARITY_LOW; + else + mode_hsync_polarity = ADIHDMI_SYNC_POLARITY_HIGH; + + if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_vsync_polarity = ADIHDMI_SYNC_POLARITY_LOW; + else + mode_vsync_polarity = ADIHDMI_SYNC_POLARITY_HIGH; + + if (adihdmi->hsync_polarity != mode_hsync_polarity && + adihdmi->hsync_polarity != + ADIHDMI_SYNC_POLARITY_PASSTHROUGH) + hsync_polarity = 1; + + if (adihdmi->vsync_polarity != mode_vsync_polarity && + adihdmi->vsync_polarity != + ADIHDMI_SYNC_POLARITY_PASSTHROUGH) + vsync_polarity = 1; + } + + if (mode->vrefresh <= 24000) + low_refresh_rate = ADIHDMI_LOW_REFRESH_RATE_24HZ; + else if (mode->vrefresh <= 25000) + low_refresh_rate = ADIHDMI_LOW_REFRESH_RATE_25HZ; + else if (mode->vrefresh <= 30000) + low_refresh_rate = ADIHDMI_LOW_REFRESH_RATE_30HZ; + else + low_refresh_rate = ADIHDMI_LOW_REFRESH_RATE_NONE; + + regmap_update_bits(adihdmi->regmap, 0xfb, + 0x6, low_refresh_rate << 1); + regmap_update_bits(adihdmi->regmap, 0x17, + 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + + /* + * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is + * supposed to give better results. + */ + + adihdmi->f_tmds = mode->clock; +} + +static void adihdmi_encoder_restore(struct drm_encoder *encoder) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); +} + +static void adihdmi_encoder_save(struct drm_encoder *encoder) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); +} + +static void adihdmi_encoder_prepare(struct drm_encoder *encoder) +{ + adihdmi_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void adihdmi_encoder_commit(struct drm_encoder *encoder) +{ + adihdmi_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static struct drm_encoder_helper_funcs adihdmi_encoder_helper_funcs = { + .dpms = adihdmi_encoder_dpms, + .save = adihdmi_encoder_save, + .restore = adihdmi_encoder_restore, + .mode_fixup = adihdmi_encoder_mode_fixup, + .prepare = adihdmi_encoder_prepare, + .commit = adihdmi_encoder_commit, + .mode_set = adihdmi_encoder_mode_set, +}; + +static void adihdmi_encoder_destroy(struct drm_encoder *encoder) +{ + struct adihdmi2 *priv = enc_to_adihdmi2(encoder); + + adihdmi_destroy(&priv->base); + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs adihdmi_encoder_funcs = { + .destroy = adihdmi_encoder_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Slave operations + */ + +static int adihdmi_encoder_slave_create_resources(struct drm_encoder *encoder, struct drm_connector *connector) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); + return 0; +} + +static void adihdmi_encoder_slave_destroy(struct drm_encoder *encoder) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); +} + + static enum drm_connector_status +adihdmi_encoder_slave_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return adihdmi_encoder_detect(enc_to_adihdmi(encoder), + connector); +} + +static int adihdmi_encoder_slave_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + return adihdmi_encoder_get_modes(enc_to_adihdmi(encoder), + connector); +} + + +static void adihdmi_encoder_slave_set_config(struct drm_encoder *encoder, void *params) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); +} + +static int adihdmi_encoder_set_property(struct drm_encoder *encoder, struct drm_connector *connector, struct drm_property *property, uint64_t val) +{ + pr_debug("%s - %d\n", __FUNCTION__, __LINE__); + return 0; +} + +static struct drm_encoder_slave_funcs adihdmi_encoder_slave_funcs = { + .create_resources = adihdmi_encoder_slave_create_resources, + .destroy = adihdmi_encoder_slave_destroy, + .detect = adihdmi_encoder_slave_detect, + .dpms = adihdmi_encoder_dpms, + .get_modes = adihdmi_encoder_slave_get_modes, + .mode_fixup = adihdmi_encoder_mode_fixup, + .mode_set = adihdmi_encoder_mode_set, + .mode_valid = adihdmi_encoder_mode_valid, + .restore = adihdmi_encoder_restore, + .save = adihdmi_encoder_save, + .set_config = adihdmi_encoder_slave_set_config, + .set_property = adihdmi_encoder_set_property, +}; + +/* ----------------------------------------------------------------------------- + * Connector operations + */ + +static int adihdmi_connector_get_modes(struct drm_connector *connector) +{ + struct adihdmi2 *priv = conn_to_adihdmi2(connector); + + return adihdmi_encoder_get_modes(&priv->base, connector); +} + +static int adihdmi_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return adihdmi_mode_valid(mode); +} + + static struct drm_encoder * +adihdmi_connector_best_encoder(struct drm_connector *connector) +{ + struct adihdmi2 *priv = conn_to_adihdmi2(connector); + + return &priv->encoder; +} + +static struct drm_connector_helper_funcs adihdmi_connector_helper_funcs = { + .get_modes = adihdmi_connector_get_modes, + .mode_valid = adihdmi_connector_mode_valid, + .best_encoder = adihdmi_connector_best_encoder, +}; + + static enum drm_connector_status +adihdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct adihdmi2 *priv = conn_to_adihdmi2(connector); + + return adihdmi_encoder_detect(&priv->base, connector); +} + +static void adihdmi_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs adihdmi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adihdmi_connector_detect, + .destroy = adihdmi_connector_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Component operations + */ + +static int adihdmi_bind(struct device *dev, struct device *master, void *data) +{ + struct i2c_client *client = to_i2c_client(dev); + struct drm_device *drm = data; + struct adihdmi2 *priv; + uint32_t crtcs = 0; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + { + pr_err("%s - %d - No memory for ADIHDMI\n", __FUNCTION__, __LINE__); + return -ENOMEM; + } + + dev_set_drvdata(dev, priv); + + if (dev->of_node) + crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + + /* If no CRTCs were found, fall back to our old behaviour */ + if (crtcs == 0) { + dev_warn(dev, "Falling back to first CRTC\n"); + crtcs = 1 << 0; + } + + priv->base.encoder = &priv->encoder; + priv->connector.interlace_allowed = 1; + priv->encoder.possible_crtcs = crtcs; + + ret = adihdmi_create(client, &priv->base); + if (ret) + return ret; + + drm_encoder_helper_add(&priv->encoder, &adihdmi_encoder_helper_funcs); + ret = drm_encoder_init(drm, &priv->encoder, &adihdmi_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + if (ret) + goto err_encoder; + + drm_connector_helper_add(&priv->connector, + &adihdmi_connector_helper_funcs); + ret = drm_connector_init(drm, &priv->connector, + &adihdmi_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + goto err_connector; + + ret = drm_connector_register(&priv->connector); + if (ret) + goto err_sysfs; + + priv->connector.encoder = &priv->encoder; + drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder); + + return 0; + +err_sysfs: + drm_connector_cleanup(&priv->connector); +err_connector: + drm_encoder_cleanup(&priv->encoder); +err_encoder: + adihdmi_destroy(&priv->base); + return ret; + + +} + +static void adihdmi_unbind(struct device *dev, struct device *master, void *data) +{ + struct adihdmi2 *priv = dev_get_drvdata(dev); + + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); + adihdmi_destroy(&priv->base); +} + +static const struct component_ops adihdmi_ops = +{ + .bind = adihdmi_bind, + .unbind = adihdmi_unbind, +}; + +/* ----------------------------------------------------------------------------- + * Init operations + */ + +static int adihdmi_probe(struct i2c_client *i2c, const struct i2c_device_id *id) +{ + return component_add(&i2c->dev, &adihdmi_ops); +} + +static int adihdmi_remove(struct i2c_client *i2c) +{ + component_del(&i2c->dev, &adihdmi_ops); + + return 0; +} + +static int adihdmi_encoder_init(struct i2c_client *i2c, struct drm_device *dev, + struct drm_encoder_slave *encoder_slave) +{ + + struct adihdmi *adihdmi; + int ret; + + adihdmi = kzalloc(sizeof(*adihdmi), GFP_KERNEL); + if (!adihdmi) + return -ENOMEM; + + adihdmi->encoder = &encoder_slave->base; + + ret = adihdmi_create(i2c, adihdmi); + if (ret) { + kfree(adihdmi); + return ret; + } + + encoder_slave->slave_priv = adihdmi; + encoder_slave->slave_funcs = &adihdmi_encoder_slave_funcs; + + return 0; +} + +static const struct i2c_device_id adihdmi_i2c_ids[] = { + { "adv7511", 0 }, + { "adv7511w", 0 }, + { "adv7513", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adihdmi_i2c_ids); + +static const struct of_device_id adihdmi_of_ids[] = { + { .compatible = "adi,adv7511", }, + { .compatible = "adi,adv7511w", }, + { .compatible = "adi,adv7513", }, + { } +}; +MODULE_DEVICE_TABLE(of, adihdmi_of_ids); + +static struct drm_i2c_encoder_driver adihdmi_driver = { + .i2c_driver = { + .driver = { + .name = "adihdmi", + .of_match_table = adihdmi_of_ids, + }, + .id_table = adihdmi_i2c_ids, + .probe = adihdmi_probe, + .remove = adihdmi_remove, + }, + + .encoder_init = adihdmi_encoder_init, +}; + +static int __init adihdmi_init(void) +{ + return drm_i2c_encoder_register(THIS_MODULE, &adihdmi_driver); +} +module_init(adihdmi_init); + +static void __exit adihdmi_exit(void) +{ + drm_i2c_encoder_unregister(&adihdmi_driver); +} +module_exit(adihdmi_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADIHDMI HDMI transmitter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/algos/i2c-algo-bit.c b/drivers/i2c/algos/i2c-algo-bit.c index 899bede..a8e89df 100644 --- a/drivers/i2c/algos/i2c-algo-bit.c +++ b/drivers/i2c/algos/i2c-algo-bit.c @@ -617,6 +617,10 @@ const struct i2c_algorithm i2c_bit_algo = { }; EXPORT_SYMBOL(i2c_bit_algo); +static const struct i2c_adapter_quirks i2c_bit_quirk_no_clk_stretch = { + .flags = I2C_AQ_NO_CLK_STRETCH, +}; + /* * registering functions to load algorithms at runtime */ @@ -635,6 +639,8 @@ static int __i2c_bit_add_bus(struct i2c_adapter *adap, /* register new adapter to i2c module... */ adap->algo = &i2c_bit_algo; adap->retries = 3; + if (bit_adap->getscl == NULL) + adap->quirks = &i2c_bit_quirk_no_clk_stretch; ret = add_adapter(adap); if (ret < 0) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 7b0aa82..f167021 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -516,7 +516,7 @@ config I2C_EFM32 config I2C_EG20T tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C" - depends on PCI && (X86_32 || COMPILE_TEST) + depends on PCI && (X86_32 || MIPS || COMPILE_TEST) help This driver is for PCH(Platform controller Hub) I2C of EG20T which is an IOH(Input/Output Hub) for x86 embedded processor. @@ -532,6 +532,7 @@ config I2C_EG20T config I2C_EMEV2 tristate "EMMA Mobile series I2C adapter" depends on HAVE_CLK + select I2C_SLAVE help If you say yes to this option, support will be included for the I2C interface on the Renesas Electronics EM/EV family of processors. @@ -662,7 +663,7 @@ config I2C_MT65XX config I2C_MV64XXX tristate "Marvell mv64xxx I2C Controller" - depends on MV64X60 || PLAT_ORION || ARCH_SUNXI + depends on MV64X60 || PLAT_ORION || ARCH_SUNXI || ARCH_MVEBU help If you say yes to this option, support will be included for the built-in I2C interface on the Marvell 64xxx line of host bridges. @@ -788,7 +789,7 @@ config I2C_QUP config I2C_RIIC tristate "Renesas RIIC adapter" - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST help If you say yes to this option, support will be included for the Renesas RIIC I2C interface. @@ -832,7 +833,7 @@ config I2C_SH7760 config I2C_SH_MOBILE tristate "SuperH Mobile I2C Controller" depends on HAS_DMA - depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST + depends on SUPERH || ARCH_RENESAS || COMPILE_TEST help If you say yes to this option, support will be included for the built-in I2C interface on the Renesas SH-Mobile processor. @@ -907,7 +908,7 @@ config I2C_TEGRA config I2C_UNIPHIER tristate "UniPhier FIFO-less I2C controller" - depends on ARCH_UNIPHIER + depends on ARCH_UNIPHIER || COMPILE_TEST help If you say yes to this option, support will be included for the UniPhier FIFO-less I2C interface embedded in PH1-LD4, PH1-sLD8, @@ -915,7 +916,7 @@ config I2C_UNIPHIER config I2C_UNIPHIER_F tristate "UniPhier FIFO-builtin I2C controller" - depends on ARCH_UNIPHIER + depends on ARCH_UNIPHIER || COMPILE_TEST help If you say yes to this option, support will be included for the UniPhier FIFO-builtin I2C interface embedded in PH1-Pro4, @@ -963,28 +964,29 @@ config I2C_XILINX will be called xilinx_i2c. config I2C_XLR - tristate "XLR I2C support" - depends on CPU_XLR + tristate "Netlogic XLR and Sigma Designs I2C support" + depends on CPU_XLR || ARCH_TANGO help This driver enables support for the on-chip I2C interface of - the Netlogic XLR/XLS MIPS processors. + the Netlogic XLR/XLS MIPS processors and Sigma Designs SOCs. This driver can also be built as a module. If so, the module will be called i2c-xlr. config I2C_XLP9XX tristate "XLP9XX I2C support" - depends on CPU_XLP || COMPILE_TEST + depends on CPU_XLP || ARCH_VULCAN || COMPILE_TEST help This driver enables support for the on-chip I2C interface of - the Broadcom XLP9xx/XLP5xx MIPS processors. + the Broadcom XLP9xx/XLP5xx MIPS and Vulcan ARM64 processors. This driver can also be built as a module. If so, the module will be called i2c-xlp9xx. config I2C_RCAR tristate "Renesas R-Car I2C Controller" - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on HAS_DMA + depends on ARCH_RENESAS || COMPILE_TEST select I2C_SLAVE help If you say yes to this option, support will be included for the diff --git a/drivers/i2c/busses/i2c-at91.c b/drivers/i2c/busses/i2c-at91.c index 10835d1..f233726 100644 --- a/drivers/i2c/busses/i2c-at91.c +++ b/drivers/i2c/busses/i2c-at91.c @@ -64,6 +64,8 @@ #define AT91_TWI_IADR 0x000c /* Internal Address Register */ #define AT91_TWI_CWGR 0x0010 /* Clock Waveform Generator Reg */ +#define AT91_TWI_CWGR_HOLD_MAX 0x1f +#define AT91_TWI_CWGR_HOLD(x) (((x) & AT91_TWI_CWGR_HOLD_MAX) << 24) #define AT91_TWI_SR 0x0020 /* Status Register */ #define AT91_TWI_TXCOMP BIT(0) /* Transmission Complete */ @@ -110,6 +112,7 @@ struct at91_twi_pdata { unsigned clk_offset; bool has_unre_flag; bool has_alt_cmd; + bool has_hold_field; struct at_dma_slave dma_slave; }; @@ -187,10 +190,11 @@ static void at91_init_twi_bus(struct at91_twi_dev *dev) */ static void at91_calc_twi_clock(struct at91_twi_dev *dev, int twi_clk) { - int ckdiv, cdiv, div; + int ckdiv, cdiv, div, hold = 0; struct at91_twi_pdata *pdata = dev->pdata; int offset = pdata->clk_offset; int max_ckdiv = pdata->clk_max_div; + u32 twd_hold_time_ns = 0; div = max(0, (int)DIV_ROUND_UP(clk_get_rate(dev->clk), 2 * twi_clk) - offset); @@ -204,8 +208,33 @@ static void at91_calc_twi_clock(struct at91_twi_dev *dev, int twi_clk) cdiv = 255; } - dev->twi_cwgr_reg = (ckdiv << 16) | (cdiv << 8) | cdiv; - dev_dbg(dev->dev, "cdiv %d ckdiv %d\n", cdiv, ckdiv); + if (pdata->has_hold_field) { + of_property_read_u32(dev->dev->of_node, "i2c-sda-hold-time-ns", + &twd_hold_time_ns); + + /* + * hold time = HOLD + 3 x T_peripheral_clock + * Use clk rate in kHz to prevent overflows when computing + * hold. + */ + hold = DIV_ROUND_UP(twd_hold_time_ns + * (clk_get_rate(dev->clk) / 1000), 1000000); + hold -= 3; + if (hold < 0) + hold = 0; + if (hold > AT91_TWI_CWGR_HOLD_MAX) { + dev_warn(dev->dev, + "HOLD field set to its maximum value (%d instead of %d)\n", + AT91_TWI_CWGR_HOLD_MAX, hold); + hold = AT91_TWI_CWGR_HOLD_MAX; + } + } + + dev->twi_cwgr_reg = (ckdiv << 16) | (cdiv << 8) | cdiv + | AT91_TWI_CWGR_HOLD(hold); + + dev_dbg(dev->dev, "cdiv %d ckdiv %d hold %d (%d ns)\n", + cdiv, ckdiv, hold, twd_hold_time_ns); } static void at91_twi_dma_cleanup(struct at91_twi_dev *dev) @@ -797,6 +826,7 @@ static struct at91_twi_pdata at91rm9200_config = { .clk_offset = 3, .has_unre_flag = true, .has_alt_cmd = false, + .has_hold_field = false, }; static struct at91_twi_pdata at91sam9261_config = { @@ -804,6 +834,7 @@ static struct at91_twi_pdata at91sam9261_config = { .clk_offset = 4, .has_unre_flag = false, .has_alt_cmd = false, + .has_hold_field = false, }; static struct at91_twi_pdata at91sam9260_config = { @@ -811,6 +842,7 @@ static struct at91_twi_pdata at91sam9260_config = { .clk_offset = 4, .has_unre_flag = false, .has_alt_cmd = false, + .has_hold_field = false, }; static struct at91_twi_pdata at91sam9g20_config = { @@ -818,6 +850,7 @@ static struct at91_twi_pdata at91sam9g20_config = { .clk_offset = 4, .has_unre_flag = false, .has_alt_cmd = false, + .has_hold_field = false, }; static struct at91_twi_pdata at91sam9g10_config = { @@ -825,6 +858,7 @@ static struct at91_twi_pdata at91sam9g10_config = { .clk_offset = 4, .has_unre_flag = false, .has_alt_cmd = false, + .has_hold_field = false, }; static const struct platform_device_id at91_twi_devtypes[] = { @@ -854,6 +888,15 @@ static struct at91_twi_pdata at91sam9x5_config = { .clk_offset = 4, .has_unre_flag = false, .has_alt_cmd = false, + .has_hold_field = false, +}; + +static struct at91_twi_pdata sama5d4_config = { + .clk_max_div = 7, + .clk_offset = 4, + .has_unre_flag = false, + .has_alt_cmd = false, + .has_hold_field = true, }; static struct at91_twi_pdata sama5d2_config = { @@ -861,6 +904,7 @@ static struct at91_twi_pdata sama5d2_config = { .clk_offset = 4, .has_unre_flag = true, .has_alt_cmd = true, + .has_hold_field = true, }; static const struct of_device_id atmel_twi_dt_ids[] = { @@ -883,6 +927,9 @@ static const struct of_device_id atmel_twi_dt_ids[] = { .compatible = "atmel,at91sam9x5-i2c", .data = &at91sam9x5_config, }, { + .compatible = "atmel,sama5d4-i2c", + .data = &sama5d4_config, + }, { .compatible = "atmel,sama5d2-i2c", .data = &sama5d2_config, }, { @@ -966,7 +1013,7 @@ static int at91_twi_configure_dma(struct at91_twi_dev *dev, u32 phy_addr) error: if (ret != -EPROBE_DEFER) - dev_info(dev->dev, "can't use DMA, error %d\n", ret); + dev_info(dev->dev, "can't get DMA channel, continue without DMA support\n"); if (dma->chan_rx) dma_release_channel(dma->chan_rx); if (dma->chan_tx) diff --git a/drivers/i2c/busses/i2c-bcm-iproc.c b/drivers/i2c/busses/i2c-bcm-iproc.c index 0419f52..19c8438 100644 --- a/drivers/i2c/busses/i2c-bcm-iproc.c +++ b/drivers/i2c/busses/i2c-bcm-iproc.c @@ -58,11 +58,13 @@ #define IE_M_RX_FIFO_FULL_SHIFT 31 #define IE_M_RX_THLD_SHIFT 30 #define IE_M_START_BUSY_SHIFT 28 +#define IE_M_TX_UNDERRUN_SHIFT 27 #define IS_OFFSET 0x3c #define IS_M_RX_FIFO_FULL_SHIFT 31 #define IS_M_RX_THLD_SHIFT 30 #define IS_M_START_BUSY_SHIFT 28 +#define IS_M_TX_UNDERRUN_SHIFT 27 #define M_TX_OFFSET 0x40 #define M_TX_WR_STATUS_SHIFT 31 @@ -76,7 +78,7 @@ #define M_RX_DATA_SHIFT 0 #define M_RX_DATA_MASK 0xff -#define I2C_TIMEOUT_MESC 100 +#define I2C_TIMEOUT_MSEC 50000 #define M_TX_RX_FIFO_SIZE 64 enum bus_speed_index { @@ -95,12 +97,17 @@ struct bcm_iproc_i2c_dev { struct completion done; int xfer_is_done; + + struct i2c_msg *msg; + + /* bytes that have been transferred */ + unsigned int tx_bytes; }; /* * Can be expanded in the future if more interrupt status bits are utilized */ -#define ISR_MASK (1 << IS_M_START_BUSY_SHIFT) +#define ISR_MASK (BIT(IS_M_START_BUSY_SHIFT) | BIT(IS_M_TX_UNDERRUN_SHIFT)) static irqreturn_t bcm_iproc_i2c_isr(int irq, void *data) { @@ -112,13 +119,95 @@ static irqreturn_t bcm_iproc_i2c_isr(int irq, void *data) if (!status) return IRQ_NONE; + /* TX FIFO is empty and we have more data to send */ + if (status & BIT(IS_M_TX_UNDERRUN_SHIFT)) { + struct i2c_msg *msg = iproc_i2c->msg; + unsigned int tx_bytes = msg->len - iproc_i2c->tx_bytes; + unsigned int i; + u32 val; + + /* can only fill up to the FIFO size */ + tx_bytes = min_t(unsigned int, tx_bytes, M_TX_RX_FIFO_SIZE); + for (i = 0; i < tx_bytes; i++) { + /* start from where we left over */ + unsigned int idx = iproc_i2c->tx_bytes + i; + + val = msg->buf[idx]; + + /* mark the last byte */ + if (idx == msg->len - 1) { + u32 tmp; + + val |= BIT(M_TX_WR_STATUS_SHIFT); + + /* + * Since this is the last byte, we should + * now disable TX FIFO underrun interrupt + */ + tmp = readl(iproc_i2c->base + IE_OFFSET); + tmp &= ~BIT(IE_M_TX_UNDERRUN_SHIFT); + writel(tmp, iproc_i2c->base + IE_OFFSET); + } + + /* load data into TX FIFO */ + writel(val, iproc_i2c->base + M_TX_OFFSET); + } + /* update number of transferred bytes */ + iproc_i2c->tx_bytes += tx_bytes; + } + + if (status & BIT(IS_M_START_BUSY_SHIFT)) { + iproc_i2c->xfer_is_done = 1; + complete_all(&iproc_i2c->done); + } + writel(status, iproc_i2c->base + IS_OFFSET); - iproc_i2c->xfer_is_done = 1; - complete_all(&iproc_i2c->done); return IRQ_HANDLED; } +static int bcm_iproc_i2c_init(struct bcm_iproc_i2c_dev *iproc_i2c) +{ + u32 val; + + /* put controller in reset */ + val = readl(iproc_i2c->base + CFG_OFFSET); + val |= 1 << CFG_RESET_SHIFT; + val &= ~(1 << CFG_EN_SHIFT); + writel(val, iproc_i2c->base + CFG_OFFSET); + + /* wait 100 usec per spec */ + udelay(100); + + /* bring controller out of reset */ + val &= ~(1 << CFG_RESET_SHIFT); + writel(val, iproc_i2c->base + CFG_OFFSET); + + /* flush TX/RX FIFOs and set RX FIFO threshold to zero */ + val = (1 << M_FIFO_RX_FLUSH_SHIFT) | (1 << M_FIFO_TX_FLUSH_SHIFT); + writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET); + /* disable all interrupts */ + writel(0, iproc_i2c->base + IE_OFFSET); + + /* clear all pending interrupts */ + writel(0xffffffff, iproc_i2c->base + IS_OFFSET); + + return 0; +} + +static void bcm_iproc_i2c_enable_disable(struct bcm_iproc_i2c_dev *iproc_i2c, + bool enable) +{ + u32 val; + + val = readl(iproc_i2c->base + CFG_OFFSET); + if (enable) + val |= BIT(CFG_EN_SHIFT); + else + val &= ~BIT(CFG_EN_SHIFT); + writel(val, iproc_i2c->base + CFG_OFFSET); +} + static int bcm_iproc_i2c_check_status(struct bcm_iproc_i2c_dev *iproc_i2c, struct i2c_msg *msg) { @@ -149,6 +238,12 @@ static int bcm_iproc_i2c_check_status(struct bcm_iproc_i2c_dev *iproc_i2c, default: dev_dbg(iproc_i2c->device, "unknown error code=%d\n", val); + + /* re-initialize i2c for recovery */ + bcm_iproc_i2c_enable_disable(iproc_i2c, false); + bcm_iproc_i2c_init(iproc_i2c); + bcm_iproc_i2c_enable_disable(iproc_i2c, true); + return -EIO; } } @@ -159,7 +254,8 @@ static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c, int ret, i; u8 addr; u32 val; - unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MESC); + unsigned int tx_bytes; + unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MSEC); /* check if bus is busy */ if (!!(readl(iproc_i2c->base + M_CMD_OFFSET) & @@ -168,13 +264,20 @@ static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c, return -EBUSY; } + iproc_i2c->msg = msg; + /* format and load slave address into the TX FIFO */ - addr = msg->addr << 1 | (msg->flags & I2C_M_RD ? 1 : 0); + addr = i2c_8bit_addr_from_msg(msg); writel(addr, iproc_i2c->base + M_TX_OFFSET); - /* for a write transaction, load data into the TX FIFO */ + /* + * For a write transaction, load data into the TX FIFO. Only allow + * loading up to TX FIFO size - 1 bytes of data since the first byte + * has been used up by the slave address + */ + tx_bytes = min_t(unsigned int, msg->len, M_TX_RX_FIFO_SIZE - 1); if (!(msg->flags & I2C_M_RD)) { - for (i = 0; i < msg->len; i++) { + for (i = 0; i < tx_bytes; i++) { val = msg->buf[i]; /* mark the last byte */ @@ -183,6 +286,7 @@ static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c, writel(val, iproc_i2c->base + M_TX_OFFSET); } + iproc_i2c->tx_bytes = tx_bytes; } /* mark as incomplete before starting the transaction */ @@ -194,13 +298,24 @@ static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c, * transaction is done, i.e., the internal start_busy bit, transitions * from 1 to 0. */ - writel(1 << IE_M_START_BUSY_SHIFT, iproc_i2c->base + IE_OFFSET); + val = BIT(IE_M_START_BUSY_SHIFT); + + /* + * If TX data size is larger than the TX FIFO, need to enable TX + * underrun interrupt, which will be triggerred when the TX FIFO is + * empty. When that happens we can then pump more data into the FIFO + */ + if (!(msg->flags & I2C_M_RD) && + msg->len > iproc_i2c->tx_bytes) + val |= BIT(IE_M_TX_UNDERRUN_SHIFT); + + writel(val, iproc_i2c->base + IE_OFFSET); /* * Now we can activate the transfer. For a read operation, specify the * number of bytes to read */ - val = 1 << M_CMD_START_BUSY_SHIFT; + val = BIT(M_CMD_START_BUSY_SHIFT); if (msg->flags & I2C_M_RD) { val |= (M_CMD_PROTOCOL_BLK_RD << M_CMD_PROTOCOL_SHIFT) | (msg->len << M_CMD_RD_CNT_SHIFT); @@ -283,7 +398,6 @@ static const struct i2c_algorithm bcm_iproc_algo = { static struct i2c_adapter_quirks bcm_iproc_i2c_quirks = { /* need to reserve one byte in the FIFO for the slave address */ .max_read_len = M_TX_RX_FIFO_SIZE - 1, - .max_write_len = M_TX_RX_FIFO_SIZE - 1, }; static int bcm_iproc_i2c_cfg_speed(struct bcm_iproc_i2c_dev *iproc_i2c) @@ -321,49 +435,6 @@ static int bcm_iproc_i2c_cfg_speed(struct bcm_iproc_i2c_dev *iproc_i2c) return 0; } -static int bcm_iproc_i2c_init(struct bcm_iproc_i2c_dev *iproc_i2c) -{ - u32 val; - - /* put controller in reset */ - val = readl(iproc_i2c->base + CFG_OFFSET); - val |= 1 << CFG_RESET_SHIFT; - val &= ~(1 << CFG_EN_SHIFT); - writel(val, iproc_i2c->base + CFG_OFFSET); - - /* wait 100 usec per spec */ - udelay(100); - - /* bring controller out of reset */ - val &= ~(1 << CFG_RESET_SHIFT); - writel(val, iproc_i2c->base + CFG_OFFSET); - - /* flush TX/RX FIFOs and set RX FIFO threshold to zero */ - val = (1 << M_FIFO_RX_FLUSH_SHIFT) | (1 << M_FIFO_TX_FLUSH_SHIFT); - writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET); - - /* disable all interrupts */ - writel(0, iproc_i2c->base + IE_OFFSET); - - /* clear all pending interrupts */ - writel(0xffffffff, iproc_i2c->base + IS_OFFSET); - - return 0; -} - -static void bcm_iproc_i2c_enable_disable(struct bcm_iproc_i2c_dev *iproc_i2c, - bool enable) -{ - u32 val; - - val = readl(iproc_i2c->base + CFG_OFFSET); - if (enable) - val |= BIT(CFG_EN_SHIFT); - else - val &= ~BIT(CFG_EN_SHIFT); - writel(val, iproc_i2c->base + CFG_OFFSET); -} - static int bcm_iproc_i2c_probe(struct platform_device *pdev) { int irq, ret = 0; diff --git a/drivers/i2c/busses/i2c-bcm-kona.c b/drivers/i2c/busses/i2c-bcm-kona.c index 2c9d9b1..ac9f476 100644 --- a/drivers/i2c/busses/i2c-bcm-kona.c +++ b/drivers/i2c/busses/i2c-bcm-kona.c @@ -501,10 +501,7 @@ static int bcm_kona_i2c_do_addr(struct bcm_kona_i2c_dev *dev, return -EREMOTEIO; } } else { - addr = msg->addr << 1; - - if (msg->flags & I2C_M_RD) - addr |= 1; + addr = i2c_8bit_addr_from_msg(msg); if (bcm_kona_i2c_write_byte(dev, addr, 0) < 0) return -EREMOTEIO; diff --git a/drivers/i2c/busses/i2c-bcm2835.c b/drivers/i2c/busses/i2c-bcm2835.c index 3032b89..818b051 100644 --- a/drivers/i2c/busses/i2c-bcm2835.c +++ b/drivers/i2c/busses/i2c-bcm2835.c @@ -222,6 +222,15 @@ static const struct i2c_algorithm bcm2835_i2c_algo = { .functionality = bcm2835_i2c_func, }; +/* + * This HW was reported to have problems with clock stretching: + * http://www.advamation.com/knowhow/raspberrypi/rpi-i2c-bug.html + * https://www.raspberrypi.org/forums/viewtopic.php?p=146272 + */ +static const struct i2c_adapter_quirks bcm2835_i2c_quirks = { + .flags = I2C_AQ_NO_CLK_STRETCH, +}; + static int bcm2835_i2c_probe(struct platform_device *pdev) { struct bcm2835_i2c_dev *i2c_dev; @@ -293,6 +302,7 @@ static int bcm2835_i2c_probe(struct platform_device *pdev) adap->algo = &bcm2835_i2c_algo; adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; + adap->quirks = &bcm2835_i2c_quirks; bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, 0); diff --git a/drivers/i2c/busses/i2c-brcmstb.c b/drivers/i2c/busses/i2c-brcmstb.c index 81115ab..6a8cfc1 100644 --- a/drivers/i2c/busses/i2c-brcmstb.c +++ b/drivers/i2c/busses/i2c-brcmstb.c @@ -25,13 +25,16 @@ #include #define N_DATA_REGS 8 -#define N_DATA_BYTES (N_DATA_REGS * 4) -/* BSC count register field definitions */ -#define BSC_CNT_REG1_MASK 0x0000003f -#define BSC_CNT_REG1_SHIFT 0 -#define BSC_CNT_REG2_MASK 0x00000fc0 -#define BSC_CNT_REG2_SHIFT 6 +/* + * PER_I2C/BSC count register mask depends on 1 byte/4 byte data register + * size. Cable modem and DSL SoCs with Peripheral i2c cores use 1 byte per + * data register whereas STB SoCs use 4 byte per data register transfer, + * account for this difference in total count per transaction and mask to + * use. + */ +#define BSC_CNT_REG1_MASK(nb) (nb == 1 ? GENMASK(3, 0) : GENMASK(5, 0)) +#define BSC_CNT_REG1_SHIFT 0 /* BSC CTL register field definitions */ #define BSC_CTL_REG_DTF_MASK 0x00000003 @@ -41,7 +44,7 @@ #define BSC_CTL_REG_INT_EN_SHIFT 6 #define BSC_CTL_REG_DIV_CLK_MASK 0x00000080 -/* BSC_IIC_ENABLE r/w enable and interrupt field defintions */ +/* BSC_IIC_ENABLE r/w enable and interrupt field definitions */ #define BSC_IIC_EN_RESTART_MASK 0x00000040 #define BSC_IIC_EN_NOSTART_MASK 0x00000020 #define BSC_IIC_EN_NOSTOP_MASK 0x00000010 @@ -169,6 +172,7 @@ struct brcmstb_i2c_dev { struct completion done; bool is_suspended; u32 clk_freq_hz; + int data_regsz; }; /* register accessors for both be and le cpu arch */ @@ -186,6 +190,16 @@ struct brcmstb_i2c_dev { #define bsc_writel(_dev, _val, _reg) \ __bsc_writel(_val, _dev->base + offsetof(struct bsc_regs, _reg)) +static inline int brcmstb_i2c_get_xfersz(struct brcmstb_i2c_dev *dev) +{ + return (N_DATA_REGS * dev->data_regsz); +} + +static inline int brcmstb_i2c_get_data_regsz(struct brcmstb_i2c_dev *dev) +{ + return dev->data_regsz; +} + static void brcmstb_i2c_enable_disable_irq(struct brcmstb_i2c_dev *dev, bool int_en) { @@ -323,14 +337,16 @@ static int brcmstb_i2c_xfer_bsc_data(struct brcmstb_i2c_dev *dev, u8 *buf, unsigned int len, struct i2c_msg *pmsg) { - int cnt, byte, rc; + int cnt, byte, i, rc; enum bsc_xfer_cmd cmd; u32 ctl_reg; struct bsc_regs *pi2creg = dev->bsc_regmap; int no_ack = pmsg->flags & I2C_M_IGNORE_NAK; + int data_regsz = brcmstb_i2c_get_data_regsz(dev); + int xfersz = brcmstb_i2c_get_xfersz(dev); /* see if the transaction needs to check NACK conditions */ - if (no_ack || len <= N_DATA_BYTES) { + if (no_ack || len <= xfersz) { cmd = (pmsg->flags & I2C_M_RD) ? CMD_RD_NOACK : CMD_WR_NOACK; pi2creg->ctlhi_reg |= BSC_CTLHI_REG_IGNORE_ACK_MASK; @@ -348,20 +364,22 @@ static int brcmstb_i2c_xfer_bsc_data(struct brcmstb_i2c_dev *dev, pi2creg->ctl_reg = ctl_reg | DTF_RD_MASK; /* set the read/write length */ - bsc_writel(dev, BSC_CNT_REG1_MASK & (len << BSC_CNT_REG1_SHIFT), - cnt_reg); + bsc_writel(dev, BSC_CNT_REG1_MASK(data_regsz) & + (len << BSC_CNT_REG1_SHIFT), cnt_reg); /* Write data into data_in register */ + if (cmd == CMD_WR || cmd == CMD_WR_NOACK) { - for (cnt = 0; cnt < len; cnt += 4) { + for (cnt = 0, i = 0; cnt < len; cnt += data_regsz, i++) { u32 word = 0; - for (byte = 0; byte < 4; byte++) { - word >>= 8; + for (byte = 0; byte < data_regsz; byte++) { + word >>= BITS_PER_BYTE; if ((cnt + byte) < len) - word |= buf[cnt + byte] << 24; + word |= buf[cnt + byte] << + (BITS_PER_BYTE * (data_regsz - 1)); } - bsc_writel(dev, word, data_in[cnt >> 2]); + bsc_writel(dev, word, data_in[i]); } } @@ -373,14 +391,15 @@ static int brcmstb_i2c_xfer_bsc_data(struct brcmstb_i2c_dev *dev, return rc; } + /* Read data from data_out register */ if (cmd == CMD_RD || cmd == CMD_RD_NOACK) { - for (cnt = 0; cnt < len; cnt += 4) { - u32 data = bsc_readl(dev, data_out[cnt >> 2]); + for (cnt = 0, i = 0; cnt < len; cnt += data_regsz, i++) { + u32 data = bsc_readl(dev, data_out[i]); - for (byte = 0; byte < 4 && + for (byte = 0; byte < data_regsz && (byte + cnt) < len; byte++) { buf[cnt + byte] = data & 0xff; - data >>= 8; + data >>= BITS_PER_BYTE; } } } @@ -427,9 +446,7 @@ static int brcmstb_i2c_do_addr(struct brcmstb_i2c_dev *dev, } } else { - addr = msg->addr << 1; - if (msg->flags & I2C_M_RD) - addr |= 1; + addr = i2c_8bit_addr_from_msg(msg); bsc_writel(dev, addr, chip_address); } @@ -448,6 +465,7 @@ static int brcmstb_i2c_xfer(struct i2c_adapter *adapter, int bytes_to_xfer; u8 *tmp_buf; int len = 0; + int xfersz = brcmstb_i2c_get_xfersz(dev); if (dev->is_suspended) return -EBUSY; @@ -482,9 +500,9 @@ static int brcmstb_i2c_xfer(struct i2c_adapter *adapter, /* Perform data transfer */ while (len) { - bytes_to_xfer = min(len, N_DATA_BYTES); + bytes_to_xfer = min(len, xfersz); - if (len <= N_DATA_BYTES && i == (num - 1)) + if (len <= xfersz && i == (num - 1)) brcmstb_set_i2c_start_stop(dev, ~(COND_START_STOP)); @@ -542,8 +560,12 @@ static void brcmstb_i2c_set_bus_speed(struct brcmstb_i2c_dev *dev) static void brcmstb_i2c_set_bsc_reg_defaults(struct brcmstb_i2c_dev *dev) { - /* 4 byte data register */ - dev->bsc_regmap->ctlhi_reg = BSC_CTLHI_REG_DATAREG_SIZE_MASK; + if (brcmstb_i2c_get_data_regsz(dev) == sizeof(u32)) + /* set 4 byte data in/out xfers */ + dev->bsc_regmap->ctlhi_reg = BSC_CTLHI_REG_DATAREG_SIZE_MASK; + else + dev->bsc_regmap->ctlhi_reg &= ~BSC_CTLHI_REG_DATAREG_SIZE_MASK; + bsc_writel(dev, dev->bsc_regmap->ctlhi_reg, ctlhi_reg); /* set bus speed */ brcmstb_i2c_set_bus_speed(dev); @@ -607,6 +629,13 @@ static int brcmstb_i2c_probe(struct platform_device *pdev) dev->clk_freq_hz = bsc_clk[0].hz; } + /* set the data in/out register size for compatible SoCs */ + if (of_device_is_compatible(dev->device->of_node, + "brcmstb,brcmper-i2c")) + dev->data_regsz = sizeof(u8); + else + dev->data_regsz = sizeof(u32); + brcmstb_i2c_set_bsc_reg_defaults(dev); /* Add the i2c adapter */ @@ -673,6 +702,7 @@ static SIMPLE_DEV_PM_OPS(brcmstb_i2c_pm, brcmstb_i2c_suspend, static const struct of_device_id brcmstb_i2c_of_match[] = { {.compatible = "brcm,brcmstb-i2c"}, + {.compatible = "brcm,brcmper-i2c"}, {}, }; MODULE_DEVICE_TABLE(of, brcmstb_i2c_of_match); diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c index 84deed6..90bbd9f 100644 --- a/drivers/i2c/busses/i2c-cadence.c +++ b/drivers/i2c/busses/i2c-cadence.c @@ -18,6 +18,7 @@ #include #include #include +#include /* Register offsets for the I2C device. */ #define CDNS_I2C_CR_OFFSET 0x00 /* Control Register, RW */ @@ -96,6 +97,8 @@ CDNS_I2C_IXR_COMP) #define CDNS_I2C_TIMEOUT msecs_to_jiffies(1000) +/* timeout for pm runtime autosuspend */ +#define CNDS_I2C_PM_TIMEOUT 1000 /* ms */ #define CDNS_I2C_FIFO_DEPTH 16 /* FIFO depth at which the DATA interrupt occurs */ @@ -121,6 +124,8 @@ /** * struct cdns_i2c - I2C device private data structure + * + * @dev: Pointer to device structure * @membase: Base address of the I2C device * @adap: I2C adapter instance * @p_msg: Message pointer @@ -128,7 +133,6 @@ * @xfer_done: Transfer complete status * @p_send_buf: Pointer to transmit buffer * @p_recv_buf: Pointer to receive buffer - * @suspended: Flag holding the device's PM status * @send_count: Number of bytes still expected to send * @recv_count: Number of bytes still expected to receive * @curr_recv_count: Number of bytes to be received in current transfer @@ -141,6 +145,7 @@ * @quirks: flag for broken hold bit usage in r1p10 */ struct cdns_i2c { + struct device *dev; void __iomem *membase; struct i2c_adapter adap; struct i2c_msg *p_msg; @@ -148,7 +153,6 @@ struct cdns_i2c { struct completion xfer_done; unsigned char *p_send_buf; unsigned char *p_recv_buf; - u8 suspended; unsigned int send_count; unsigned int recv_count; unsigned int curr_recv_count; @@ -169,7 +173,7 @@ struct cdns_platform_data { clk_rate_change_nb) /** - * cdns_i2c_clear_bus_hold() - Clear bus hold bit + * cdns_i2c_clear_bus_hold - Clear bus hold bit * @id: Pointer to driver data struct * * Helper to clear the controller's bus hold bit. @@ -569,9 +573,14 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, struct cdns_i2c *id = adap->algo_data; bool hold_quirk; + ret = pm_runtime_get_sync(id->dev); + if (ret < 0) + return ret; /* Check if the bus is free */ - if (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_BA) - return -EAGAIN; + if (cdns_i2c_readreg(CDNS_I2C_SR_OFFSET) & CDNS_I2C_SR_BA) { + ret = -EAGAIN; + goto out; + } hold_quirk = !!(id->quirks & CDNS_I2C_BROKEN_HOLD_BIT); /* @@ -590,7 +599,8 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, if (msgs[count].flags & I2C_M_RD) { dev_warn(adap->dev.parent, "Can't do repeated start after a receive message\n"); - return -EOPNOTSUPP; + ret = -EOPNOTSUPP; + goto out; } } id->bus_hold_flag = 1; @@ -608,20 +618,26 @@ static int cdns_i2c_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, ret = cdns_i2c_process_msg(id, msgs, adap); if (ret) - return ret; + goto out; /* Report the other error interrupts to application */ if (id->err_status) { cdns_i2c_master_reset(adap); - if (id->err_status & CDNS_I2C_IXR_NACK) - return -ENXIO; - - return -EIO; + if (id->err_status & CDNS_I2C_IXR_NACK) { + ret = -ENXIO; + goto out; + } + ret = -EIO; + goto out; } } - return num; + ret = num; +out: + pm_runtime_mark_last_busy(id->dev); + pm_runtime_put_autosuspend(id->dev); + return ret; } /** @@ -760,7 +776,7 @@ static int cdns_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long struct clk_notifier_data *ndata = data; struct cdns_i2c *id = to_cdns_i2c(nb); - if (id->suspended) + if (pm_runtime_suspended(id->dev)) return NOTIFY_OK; switch (event) { @@ -801,53 +817,50 @@ static int cdns_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long } /** - * cdns_i2c_suspend - Suspend method for the driver - * @_dev: Address of the platform_device structure + * cdns_i2c_runtime_suspend - Runtime suspend method for the driver + * @dev: Address of the platform_device structure * * Put the driver into low power mode. * * Return: 0 always */ -static int __maybe_unused cdns_i2c_suspend(struct device *_dev) +static int __maybe_unused cdns_i2c_runtime_suspend(struct device *dev) { - struct platform_device *pdev = container_of(_dev, - struct platform_device, dev); + struct platform_device *pdev = to_platform_device(dev); struct cdns_i2c *xi2c = platform_get_drvdata(pdev); clk_disable(xi2c->clk); - xi2c->suspended = 1; return 0; } /** - * cdns_i2c_resume - Resume from suspend - * @_dev: Address of the platform_device structure + * cdns_i2c_runtime_resume - Runtime resume + * @dev: Address of the platform_device structure * - * Resume operation after suspend. + * Runtime resume callback. * * Return: 0 on success and error value on error */ -static int __maybe_unused cdns_i2c_resume(struct device *_dev) +static int __maybe_unused cdns_i2c_runtime_resume(struct device *dev) { - struct platform_device *pdev = container_of(_dev, - struct platform_device, dev); + struct platform_device *pdev = to_platform_device(dev); struct cdns_i2c *xi2c = platform_get_drvdata(pdev); int ret; ret = clk_enable(xi2c->clk); if (ret) { - dev_err(_dev, "Cannot enable clock.\n"); + dev_err(dev, "Cannot enable clock.\n"); return ret; } - xi2c->suspended = 0; - return 0; } -static SIMPLE_DEV_PM_OPS(cdns_i2c_dev_pm_ops, cdns_i2c_suspend, - cdns_i2c_resume); +static const struct dev_pm_ops cdns_i2c_dev_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_i2c_runtime_suspend, + cdns_i2c_runtime_resume, NULL) +}; static const struct cdns_platform_data r1p10_i2c_def = { .quirks = CDNS_I2C_BROKEN_HOLD_BIT, @@ -881,6 +894,7 @@ static int cdns_i2c_probe(struct platform_device *pdev) if (!id) return -ENOMEM; + id->dev = &pdev->dev; platform_set_drvdata(pdev, id); match = of_match_node(cdns_i2c_of_match, pdev->dev.of_node); @@ -913,10 +927,14 @@ static int cdns_i2c_probe(struct platform_device *pdev) return PTR_ERR(id->clk); } ret = clk_prepare_enable(id->clk); - if (ret) { + if (ret) dev_err(&pdev->dev, "Unable to enable clock.\n"); - return ret; - } + + pm_runtime_enable(id->dev); + pm_runtime_set_autosuspend_delay(id->dev, CNDS_I2C_PM_TIMEOUT); + pm_runtime_use_autosuspend(id->dev); + pm_runtime_set_active(id->dev); + id->clk_rate_change_nb.notifier_call = cdns_i2c_clk_notifier_cb; if (clk_notifier_register(id->clk, &id->clk_rate_change_nb)) dev_warn(&pdev->dev, "Unable to register clock notifier.\n"); @@ -966,6 +984,8 @@ static int cdns_i2c_probe(struct platform_device *pdev) err_clk_dis: clk_disable_unprepare(id->clk); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); return ret; } @@ -984,6 +1004,7 @@ static int cdns_i2c_remove(struct platform_device *pdev) i2c_del_adapter(&id->adap); clk_notifier_unregister(id->clk, &id->clk_rate_change_nb); clk_disable_unprepare(id->clk); + pm_runtime_disable(&pdev->dev); return 0; } diff --git a/drivers/i2c/busses/i2c-cpm.c b/drivers/i2c/busses/i2c-cpm.c index b167ab2..ee57c1e 100644 --- a/drivers/i2c/busses/i2c-cpm.c +++ b/drivers/i2c/busses/i2c-cpm.c @@ -197,9 +197,7 @@ static void cpm_i2c_parse_message(struct i2c_adapter *adap, tbdf = cpm->tbase + tx; rbdf = cpm->rbase + rx; - addr = pmsg->addr << 1; - if (pmsg->flags & I2C_M_RD) - addr |= 1; + addr = i2c_8bit_addr_from_msg(pmsg); tb = cpm->txbuf[tx]; rb = cpm->rxbuf[rx]; diff --git a/drivers/i2c/busses/i2c-designware-baytrail.c b/drivers/i2c/busses/i2c-designware-baytrail.c index 7d7ae97..1590ad0 100644 --- a/drivers/i2c/busses/i2c-designware-baytrail.c +++ b/drivers/i2c/busses/i2c-designware-baytrail.c @@ -11,7 +11,6 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ -#include #include #include #include @@ -34,8 +33,7 @@ static int get_sem(struct device *dev, u32 *sem) u32 data; int ret; - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, PUNIT_SEMAPHORE, - &data); + ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, PUNIT_SEMAPHORE, &data); if (ret) { dev_err(dev, "iosf failed to read punit semaphore\n"); return ret; @@ -50,21 +48,19 @@ static void reset_semaphore(struct device *dev) { u32 data; - if (iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - PUNIT_SEMAPHORE, &data)) { + if (iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, PUNIT_SEMAPHORE, &data)) { dev_err(dev, "iosf failed to reset punit semaphore during read\n"); return; } data &= ~PUNIT_SEMAPHORE_BIT; - if (iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - PUNIT_SEMAPHORE, data)) + if (iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, PUNIT_SEMAPHORE, data)) dev_err(dev, "iosf failed to reset punit semaphore during write\n"); } static int baytrail_i2c_acquire(struct dw_i2c_dev *dev) { - u32 sem; + u32 sem = PUNIT_SEMAPHORE_ACQUIRE; int ret; unsigned long start, end; @@ -77,8 +73,7 @@ static int baytrail_i2c_acquire(struct dw_i2c_dev *dev) return 0; /* host driver writes to side band semaphore register */ - ret = iosf_mbi_write(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_WRITE, - PUNIT_SEMAPHORE, PUNIT_SEMAPHORE_ACQUIRE); + ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE, PUNIT_SEMAPHORE, sem); if (ret) { dev_err(dev->dev, "iosf punit semaphore request failed\n"); return ret; @@ -102,8 +97,7 @@ static int baytrail_i2c_acquire(struct dw_i2c_dev *dev) dev_err(dev->dev, "punit semaphore timed out, resetting\n"); reset_semaphore(dev->dev); - ret = iosf_mbi_read(BT_MBI_UNIT_PMC, BT_MBI_BUNIT_READ, - PUNIT_SEMAPHORE, &sem); + ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ, PUNIT_SEMAPHORE, &sem); if (ret) dev_err(dev->dev, "iosf failed to read punit semaphore\n"); else @@ -156,7 +150,3 @@ int i2c_dw_eval_lock_support(struct dw_i2c_dev *dev) return 0; } - -MODULE_AUTHOR("David E. Box "); -MODULE_DESCRIPTION("Baytrail I2C Semaphore driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-designware-core.c b/drivers/i2c/busses/i2c-designware-core.c index de7fbbb..99b54be 100644 --- a/drivers/i2c/busses/i2c-designware-core.c +++ b/drivers/i2c/busses/i2c-designware-core.c @@ -271,6 +271,17 @@ static void __i2c_dw_enable(struct dw_i2c_dev *dev, bool enable) enable ? "en" : "dis"); } +static unsigned long i2c_dw_clk_rate(struct dw_i2c_dev *dev) +{ + /* + * Clock is not necessary if we got LCNT/HCNT values directly from + * the platform code. + */ + if (WARN_ON_ONCE(!dev->get_clk_rate_khz)) + return 0; + return dev->get_clk_rate_khz(dev); +} + /** * i2c_dw_init() - initialize the designware i2c master hardware * @dev: device private data @@ -281,7 +292,6 @@ static void __i2c_dw_enable(struct dw_i2c_dev *dev, bool enable) */ int i2c_dw_init(struct dw_i2c_dev *dev) { - u32 input_clock_khz; u32 hcnt, lcnt; u32 reg; u32 sda_falling_time, scl_falling_time; @@ -295,8 +305,6 @@ int i2c_dw_init(struct dw_i2c_dev *dev) } } - input_clock_khz = dev->get_clk_rate_khz(dev); - reg = dw_readl(dev, DW_IC_COMP_TYPE); if (reg == ___constant_swab32(DW_IC_COMP_TYPE_VALUE)) { /* Configure register endianess access */ @@ -325,12 +333,12 @@ int i2c_dw_init(struct dw_i2c_dev *dev) hcnt = dev->ss_hcnt; lcnt = dev->ss_lcnt; } else { - hcnt = i2c_dw_scl_hcnt(input_clock_khz, + hcnt = i2c_dw_scl_hcnt(i2c_dw_clk_rate(dev), 4000, /* tHD;STA = tHIGH = 4.0 us */ sda_falling_time, 0, /* 0: DW default, 1: Ideal */ 0); /* No offset */ - lcnt = i2c_dw_scl_lcnt(input_clock_khz, + lcnt = i2c_dw_scl_lcnt(i2c_dw_clk_rate(dev), 4700, /* tLOW = 4.7 us */ scl_falling_time, 0); /* No offset */ @@ -344,12 +352,12 @@ int i2c_dw_init(struct dw_i2c_dev *dev) hcnt = dev->fs_hcnt; lcnt = dev->fs_lcnt; } else { - hcnt = i2c_dw_scl_hcnt(input_clock_khz, + hcnt = i2c_dw_scl_hcnt(i2c_dw_clk_rate(dev), 600, /* tHD;STA = tHIGH = 0.6 us */ sda_falling_time, 0, /* 0: DW default, 1: Ideal */ 0); /* No offset */ - lcnt = i2c_dw_scl_lcnt(input_clock_khz, + lcnt = i2c_dw_scl_lcnt(i2c_dw_clk_rate(dev), 1300, /* tLOW = 1.3 us */ scl_falling_time, 0); /* No offset */ @@ -626,7 +634,6 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); - mutex_lock(&dev->lock); pm_runtime_get_sync(dev->dev); reinit_completion(&dev->cmd_complete); @@ -665,11 +672,12 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) } /* - * We must disable the adapter before unlocking the &dev->lock mutex - * below. Otherwise the hardware might continue generating interrupts - * which in turn causes a race condition with the following transfer. - * Needs some more investigation if the additional interrupts are - * a hardware bug or this driver doesn't handle them correctly yet. + * We must disable the adapter before returning and signaling the end + * of the current transfer. Otherwise the hardware might continue + * generating interrupts which in turn causes a race condition with + * the following transfer. Needs some more investigation if the + * additional interrupts are a hardware bug or this driver doesn't + * handle them correctly yet. */ __i2c_dw_enable(dev, false); @@ -698,7 +706,6 @@ done: done_nolock: pm_runtime_mark_last_busy(dev->dev); pm_runtime_put_autosuspend(dev->dev); - mutex_unlock(&dev->lock); return ret; } @@ -852,7 +859,6 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) int r; init_completion(&dev->cmd_complete); - mutex_init(&dev->lock); r = i2c_dw_init(dev); if (r) @@ -860,12 +866,14 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) snprintf(adap->name, sizeof(adap->name), "Synopsys DesignWare I2C adapter"); + adap->retries = 3; adap->algo = &i2c_dw_algo; adap->dev.parent = dev->dev; i2c_set_adapdata(adap, dev); i2c_dw_disable_int(dev); - r = devm_request_irq(dev->dev, dev->irq, i2c_dw_isr, IRQF_SHARED, + r = devm_request_irq(dev->dev, dev->irq, i2c_dw_isr, + IRQF_SHARED | IRQF_COND_SUSPEND, dev_name(dev->dev), dev); if (r) { dev_err(dev->dev, "failure requesting irq %i: %d\n", @@ -873,9 +881,17 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) return r; } + /* + * Increment PM usage count during adapter registration in order to + * avoid possible spurious runtime suspend when adapter device is + * registered to the device core and immediate resume in case bus has + * registered I2C slaves that do I2C transfers in their probe. + */ + pm_runtime_get_noresume(dev->dev); r = i2c_add_numbered_adapter(adap); if (r) dev_err(dev->dev, "failure adding adapter: %d\n", r); + pm_runtime_put_noidle(dev->dev); return r; } diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index 9ffb63a..cd409e7 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -36,7 +36,6 @@ * @dev: driver model device node * @base: IO registers pointer * @cmd_complete: tx completion indicator - * @lock: protect this struct and IO registers * @clk: input reference clock * @cmd_err: run time hadware error code * @msgs: points to an array of messages currently being transfered @@ -73,7 +72,6 @@ struct dw_i2c_dev { struct device *dev; void __iomem *base; struct completion cmd_complete; - struct mutex lock; struct clk *clk; u32 (*get_clk_rate_khz) (struct dw_i2c_dev *dev); struct dw_pci_controller *controller; diff --git a/drivers/i2c/busses/i2c-designware-pcidrv.c b/drivers/i2c/busses/i2c-designware-pcidrv.c index 1543d35..7368be0 100644 --- a/drivers/i2c/busses/i2c-designware-pcidrv.c +++ b/drivers/i2c/busses/i2c-designware-pcidrv.c @@ -162,7 +162,7 @@ static struct dw_pci_controller dw_pci_controllers[] = { #ifdef CONFIG_PM static int i2c_dw_pci_suspend(struct device *dev) { - struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct pci_dev *pdev = to_pci_dev(dev); i2c_dw_disable(pci_get_drvdata(pdev)); return 0; @@ -170,7 +170,7 @@ static int i2c_dw_pci_suspend(struct device *dev) static int i2c_dw_pci_resume(struct device *dev) { - struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct pci_dev *pdev = to_pci_dev(dev); return i2c_dw_init(pci_get_drvdata(pdev)); } diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 6b00061..d656657 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,9 @@ static const struct acpi_device_id dw_i2c_acpi_match[] = { { "80860F41", 0 }, { "808622C1", 0 }, { "AMD0010", ACCESS_INTR_MASK }, + { "AMDI0010", ACCESS_INTR_MASK }, + { "AMDI0510", 0 }, + { "APMC0D0F", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, dw_i2c_acpi_match); @@ -132,12 +136,24 @@ static inline int dw_i2c_acpi_configure(struct platform_device *pdev) } #endif +static int i2c_dw_plat_prepare_clk(struct dw_i2c_dev *i_dev, bool prepare) +{ + if (IS_ERR(i_dev->clk)) + return PTR_ERR(i_dev->clk); + + if (prepare) + return clk_prepare_enable(i_dev->clk); + + clk_disable_unprepare(i_dev->clk); + return 0; +} + static int dw_i2c_plat_probe(struct platform_device *pdev) { + struct dw_i2c_platform_data *pdata = dev_get_platdata(&pdev->dev); struct dw_i2c_dev *dev; struct i2c_adapter *adap; struct resource *mem; - struct dw_i2c_platform_data *pdata; int irq, r; u32 clk_freq, ht = 0; @@ -161,33 +177,28 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) /* fast mode by default because of legacy reasons */ clk_freq = 400000; - if (has_acpi_companion(&pdev->dev)) { - dw_i2c_acpi_configure(pdev); - } else if (pdev->dev.of_node) { - of_property_read_u32(pdev->dev.of_node, - "i2c-sda-hold-time-ns", &ht); - - of_property_read_u32(pdev->dev.of_node, - "i2c-sda-falling-time-ns", - &dev->sda_falling_time); - of_property_read_u32(pdev->dev.of_node, - "i2c-scl-falling-time-ns", - &dev->scl_falling_time); - - of_property_read_u32(pdev->dev.of_node, "clock-frequency", - &clk_freq); - - /* Only standard mode at 100kHz and fast mode at 400kHz - * are supported. - */ - if (clk_freq != 100000 && clk_freq != 400000) { - dev_err(&pdev->dev, "Only 100kHz and 400kHz supported"); - return -EINVAL; - } + if (pdata) { + clk_freq = pdata->i2c_scl_freq; } else { - pdata = dev_get_platdata(&pdev->dev); - if (pdata) - clk_freq = pdata->i2c_scl_freq; + device_property_read_u32(&pdev->dev, "i2c-sda-hold-time-ns", + &ht); + device_property_read_u32(&pdev->dev, "i2c-sda-falling-time-ns", + &dev->sda_falling_time); + device_property_read_u32(&pdev->dev, "i2c-scl-falling-time-ns", + &dev->scl_falling_time); + device_property_read_u32(&pdev->dev, "clock-frequency", + &clk_freq); + } + + if (has_acpi_companion(&pdev->dev)) + dw_i2c_acpi_configure(pdev); + + /* + * Only standard mode at 100kHz and fast mode at 400kHz are supported. + */ + if (clk_freq != 100000 && clk_freq != 400000) { + dev_err(&pdev->dev, "Only 100kHz and 400kHz supported"); + return -EINVAL; } r = i2c_dw_eval_lock_support(dev); @@ -209,16 +220,13 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) DW_IC_CON_RESTART_EN | DW_IC_CON_SPEED_FAST; dev->clk = devm_clk_get(&pdev->dev, NULL); - dev->get_clk_rate_khz = i2c_dw_get_clk_rate_khz; - if (IS_ERR(dev->clk)) - return PTR_ERR(dev->clk); - clk_prepare_enable(dev->clk); + if (!i2c_dw_plat_prepare_clk(dev, true)) { + dev->get_clk_rate_khz = i2c_dw_get_clk_rate_khz; - if (!dev->sda_hold_time && ht) { - u32 ic_clk = dev->get_clk_rate_khz(dev); - - dev->sda_hold_time = div_u64((u64)ic_clk * ht + 500000, - 1000000); + if (!dev->sda_hold_time && ht) + dev->sda_hold_time = div_u64( + (u64)dev->get_clk_rate_khz(dev) * ht + 500000, + 1000000); } if (!dev->tx_fifo_depth) { @@ -300,7 +308,7 @@ static int dw_i2c_plat_suspend(struct device *dev) struct dw_i2c_dev *i_dev = platform_get_drvdata(pdev); i2c_dw_disable(i_dev); - clk_disable_unprepare(i_dev->clk); + i2c_dw_plat_prepare_clk(i_dev, false); return 0; } @@ -310,7 +318,7 @@ static int dw_i2c_plat_resume(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct dw_i2c_dev *i_dev = platform_get_drvdata(pdev); - clk_prepare_enable(i_dev->clk); + i2c_dw_plat_prepare_clk(i_dev, true); if (!i_dev->pm_runtime_disabled) i2c_dw_init(i_dev); diff --git a/drivers/i2c/busses/i2c-dln2.c b/drivers/i2c/busses/i2c-dln2.c index 1600edd..f2eb4f7 100644 --- a/drivers/i2c/busses/i2c-dln2.c +++ b/drivers/i2c/busses/i2c-dln2.c @@ -19,6 +19,7 @@ #include #include #include +#include #define DLN2_I2C_MODULE_ID 0x03 #define DLN2_I2C_CMD(cmd) DLN2_CMD(cmd, DLN2_I2C_MODULE_ID) @@ -210,6 +211,7 @@ static int dln2_i2c_probe(struct platform_device *pdev) dln2->adapter.algo = &dln2_i2c_usb_algorithm; dln2->adapter.quirks = &dln2_i2c_quirks; dln2->adapter.dev.parent = dev; + ACPI_COMPANION_SET(&dln2->adapter.dev, ACPI_COMPANION(&pdev->dev)); dln2->adapter.dev.of_node = dev->of_node; i2c_set_adapdata(&dln2->adapter, dln2); snprintf(dln2->adapter.name, sizeof(dln2->adapter.name), "%s-%s-%d", diff --git a/drivers/i2c/busses/i2c-eg20t.c b/drivers/i2c/busses/i2c-eg20t.c index 76e699f..137125b 100644 --- a/drivers/i2c/busses/i2c-eg20t.c +++ b/drivers/i2c/busses/i2c-eg20t.c @@ -795,6 +795,7 @@ static int pch_i2c_probe(struct pci_dev *pdev, /* base_addr + offset; */ adap_info->pch_data[i].pch_base_address = base_addr + 0x100 * i; + pch_adap->dev.of_node = pdev->dev.of_node; pch_adap->dev.parent = &pdev->dev; pch_i2c_init(&adap_info->pch_data[i]); diff --git a/drivers/i2c/busses/i2c-emev2.c b/drivers/i2c/busses/i2c-emev2.c index 192ef6b..96bb4e7 100644 --- a/drivers/i2c/busses/i2c-emev2.c +++ b/drivers/i2c/busses/i2c-emev2.c @@ -71,6 +71,7 @@ struct em_i2c_device { struct i2c_adapter adap; struct completion msg_done; struct clk *sclk; + struct i2c_client *slave; }; static inline void em_clear_set_bit(struct em_i2c_device *priv, u8 clear, u8 set, u8 reg) @@ -226,22 +227,131 @@ static int em_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, return num; } +static bool em_i2c_slave_irq(struct em_i2c_device *priv) +{ + u8 status, value; + enum i2c_slave_event event; + int ret; + + if (!priv->slave) + return false; + + status = readb(priv->base + I2C_OFS_IICSE0); + + /* Extension code, do not participate */ + if (status & I2C_BIT_EXC0) { + em_clear_set_bit(priv, 0, I2C_BIT_LREL0, I2C_OFS_IICC0); + return true; + } + + /* Stop detected, we don't know if it's for slave or master */ + if (status & I2C_BIT_SPD0) { + /* Notify slave device */ + i2c_slave_event(priv->slave, I2C_SLAVE_STOP, &value); + /* Pretend we did not handle the interrupt */ + return false; + } + + /* Only handle interrupts addressed to us */ + if (!(status & I2C_BIT_COI0)) + return false; + + /* Enable stop interrupts */ + em_clear_set_bit(priv, 0, I2C_BIT_SPIE0, I2C_OFS_IICC0); + + /* Transmission or Reception */ + if (status & I2C_BIT_TRC0) { + if (status & I2C_BIT_ACKD0) { + /* 9 bit interrupt mode */ + em_clear_set_bit(priv, 0, I2C_BIT_WTIM0, I2C_OFS_IICC0); + + /* Send data */ + event = status & I2C_BIT_STD0 ? + I2C_SLAVE_READ_REQUESTED : + I2C_SLAVE_READ_PROCESSED; + i2c_slave_event(priv->slave, event, &value); + writeb(value, priv->base + I2C_OFS_IIC0); + } else { + /* NACK, stop transmitting */ + em_clear_set_bit(priv, 0, I2C_BIT_LREL0, I2C_OFS_IICC0); + } + } else { + /* 8 bit interrupt mode */ + em_clear_set_bit(priv, I2C_BIT_WTIM0, I2C_BIT_ACKE0, + I2C_OFS_IICC0); + em_clear_set_bit(priv, I2C_BIT_WTIM0, I2C_BIT_WREL0, + I2C_OFS_IICC0); + + if (status & I2C_BIT_STD0) { + i2c_slave_event(priv->slave, I2C_SLAVE_WRITE_REQUESTED, + &value); + } else { + /* Recv data */ + value = readb(priv->base + I2C_OFS_IIC0); + ret = i2c_slave_event(priv->slave, + I2C_SLAVE_WRITE_RECEIVED, &value); + if (ret < 0) + em_clear_set_bit(priv, I2C_BIT_ACKE0, 0, + I2C_OFS_IICC0); + } + } + + return true; +} + static irqreturn_t em_i2c_irq_handler(int this_irq, void *dev_id) { struct em_i2c_device *priv = dev_id; + if (em_i2c_slave_irq(priv)) + return IRQ_HANDLED; + complete(&priv->msg_done); + return IRQ_HANDLED; } static u32 em_i2c_func(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SLAVE; +} + +static int em_i2c_reg_slave(struct i2c_client *slave) +{ + struct em_i2c_device *priv = i2c_get_adapdata(slave->adapter); + + if (priv->slave) + return -EBUSY; + + if (slave->flags & I2C_CLIENT_TEN) + return -EAFNOSUPPORT; + + priv->slave = slave; + + /* Set slave address */ + writeb(slave->addr << 1, priv->base + I2C_OFS_SVA0); + + return 0; +} + +static int em_i2c_unreg_slave(struct i2c_client *slave) +{ + struct em_i2c_device *priv = i2c_get_adapdata(slave->adapter); + + WARN_ON(!priv->slave); + + writeb(0, priv->base + I2C_OFS_SVA0); + + priv->slave = NULL; + + return 0; } static struct i2c_algorithm em_i2c_algo = { .master_xfer = em_i2c_xfer, .functionality = em_i2c_func, + .reg_slave = em_i2c_reg_slave, + .unreg_slave = em_i2c_unreg_slave, }; static int em_i2c_probe(struct platform_device *pdev) diff --git a/drivers/i2c/busses/i2c-exynos5.c b/drivers/i2c/busses/i2c-exynos5.c index f54ece8..c0e3ada 100644 --- a/drivers/i2c/busses/i2c-exynos5.c +++ b/drivers/i2c/busses/i2c-exynos5.c @@ -861,14 +861,8 @@ static int exynos5_i2c_resume_noirq(struct device *dev) #endif static const struct dev_pm_ops exynos5_i2c_dev_pm_ops = { -#ifdef CONFIG_PM_SLEEP - .suspend_noirq = exynos5_i2c_suspend_noirq, - .resume_noirq = exynos5_i2c_resume_noirq, - .freeze_noirq = exynos5_i2c_suspend_noirq, - .thaw_noirq = exynos5_i2c_resume_noirq, - .poweroff_noirq = exynos5_i2c_suspend_noirq, - .restore_noirq = exynos5_i2c_resume_noirq, -#endif + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(exynos5_i2c_suspend_noirq, + exynos5_i2c_resume_noirq) }; static struct platform_driver exynos5_i2c_driver = { diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index 85f39cc..4a60ad2 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -94,6 +94,7 @@ #include #include #include +#include #if (defined CONFIG_I2C_MUX_GPIO || defined CONFIG_I2C_MUX_GPIO_MODULE) && \ defined CONFIG_DMI @@ -184,7 +185,7 @@ /* Older devices have their ID defined in */ #define PCI_DEVICE_ID_INTEL_BAYTRAIL_SMBUS 0x0f12 -#define PCI_DEVICE_ID_INTEL_BRASWELL_SMBUS 0x2292 +#define PCI_DEVICE_ID_INTEL_DNV_SMBUS 0x19df #define PCI_DEVICE_ID_INTEL_COUGARPOINT_SMBUS 0x1c22 #define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS 0x1d22 /* Patsburg also has three 'Integrated Device Function' SMBus controllers */ @@ -193,9 +194,11 @@ #define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS_IDF2 0x1d72 #define PCI_DEVICE_ID_INTEL_PANTHERPOINT_SMBUS 0x1e22 #define PCI_DEVICE_ID_INTEL_AVOTON_SMBUS 0x1f3c +#define PCI_DEVICE_ID_INTEL_BRASWELL_SMBUS 0x2292 #define PCI_DEVICE_ID_INTEL_DH89XXCC_SMBUS 0x2330 #define PCI_DEVICE_ID_INTEL_COLETOCREEK_SMBUS 0x23b0 #define PCI_DEVICE_ID_INTEL_5_3400_SERIES_SMBUS 0x3b30 +#define PCI_DEVICE_ID_INTEL_BROXTON_SMBUS 0x5ad4 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_SMBUS 0x8c22 #define PCI_DEVICE_ID_INTEL_WILDCATPOINT_SMBUS 0x8ca2 #define PCI_DEVICE_ID_INTEL_WELLSBURG_SMBUS 0x8d22 @@ -204,10 +207,8 @@ #define PCI_DEVICE_ID_INTEL_WELLSBURG_SMBUS_MS2 0x8d7f #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_SMBUS 0x9c22 #define PCI_DEVICE_ID_INTEL_WILDCATPOINT_LP_SMBUS 0x9ca2 -#define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_SMBUS 0xa123 #define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_SMBUS 0x9d23 -#define PCI_DEVICE_ID_INTEL_DNV_SMBUS 0x19df -#define PCI_DEVICE_ID_INTEL_BROXTON_SMBUS 0x5ad4 +#define PCI_DEVICE_ID_INTEL_SUNRISEPOINT_H_SMBUS 0xa123 #define PCI_DEVICE_ID_INTEL_LEWISBURG_SMBUS 0xa1a3 #define PCI_DEVICE_ID_INTEL_LEWISBURG_SSKU_SMBUS 0xa223 @@ -730,6 +731,8 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr, return -EBUSY; } + pm_runtime_get_sync(&priv->pci_dev->dev); + hwpec = (priv->features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK && size != I2C_SMBUS_I2C_BLOCK_DATA; @@ -828,6 +831,8 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr, } out: + pm_runtime_mark_last_busy(&priv->pci_dev->dev); + pm_runtime_put_autosuspend(&priv->pci_dev->dev); mutex_unlock(&priv->acpi_lock); return ret; } @@ -1287,6 +1292,12 @@ i801_acpi_io_handler(u32 function, acpi_physical_address address, u32 bits, dev_warn(&pdev->dev, "BIOS is accessing SMBus registers\n"); dev_warn(&pdev->dev, "Driver SMBus register access inhibited\n"); + + /* + * BIOS is accessing the host controller so prevent it from + * suspending automatically from now on. + */ + pm_runtime_get_sync(&pdev->dev); } if ((function & ACPI_IO_MASK) == ACPI_READ) @@ -1326,6 +1337,11 @@ static void i801_acpi_remove(struct i801_priv *priv) acpi_remove_address_space_handler(adev->handle, ACPI_ADR_SPACE_SYSTEM_IO, i801_acpi_io_handler); + + mutex_lock(&priv->acpi_lock); + if (priv->acpi_reserved) + pm_runtime_put(&priv->pci_dev->dev); + mutex_unlock(&priv->acpi_lock); } #else static inline int i801_acpi_probe(struct i801_priv *priv) { return 0; } @@ -1497,6 +1513,11 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id) pci_set_drvdata(dev, priv); + pm_runtime_set_autosuspend_delay(&dev->dev, 1000); + pm_runtime_use_autosuspend(&dev->dev); + pm_runtime_put_autosuspend(&dev->dev); + pm_runtime_allow(&dev->dev); + return 0; } @@ -1504,6 +1525,9 @@ static void i801_remove(struct pci_dev *dev) { struct i801_priv *priv = pci_get_drvdata(dev); + pm_runtime_forbid(&dev->dev); + pm_runtime_get_noresume(&dev->dev); + i801_del_mux(priv); i2c_del_adapter(&priv->adapter); i801_acpi_remove(priv); @@ -1518,34 +1542,32 @@ static void i801_remove(struct pci_dev *dev) } #ifdef CONFIG_PM -static int i801_suspend(struct pci_dev *dev, pm_message_t mesg) +static int i801_suspend(struct device *dev) { - struct i801_priv *priv = pci_get_drvdata(dev); + struct pci_dev *pci_dev = to_pci_dev(dev); + struct i801_priv *priv = pci_get_drvdata(pci_dev); - pci_save_state(dev); - pci_write_config_byte(dev, SMBHSTCFG, priv->original_hstcfg); - pci_set_power_state(dev, pci_choose_state(dev, mesg)); + pci_write_config_byte(pci_dev, SMBHSTCFG, priv->original_hstcfg); return 0; } -static int i801_resume(struct pci_dev *dev) +static int i801_resume(struct device *dev) { - pci_set_power_state(dev, PCI_D0); - pci_restore_state(dev); return 0; } -#else -#define i801_suspend NULL -#define i801_resume NULL #endif +static UNIVERSAL_DEV_PM_OPS(i801_pm_ops, i801_suspend, + i801_resume, NULL); + static struct pci_driver i801_driver = { .name = "i801_smbus", .id_table = i801_ids, .probe = i801_probe, .remove = i801_remove, - .suspend = i801_suspend, - .resume = i801_resume, + .driver = { + .pm = &i801_pm_ops, + }, }; static int __init i2c_i801_init(void) diff --git a/drivers/i2c/busses/i2c-ibm_iic.c b/drivers/i2c/busses/i2c-ibm_iic.c index ab49230..cdaa7be 100644 --- a/drivers/i2c/busses/i2c-ibm_iic.c +++ b/drivers/i2c/busses/i2c-ibm_iic.c @@ -99,7 +99,7 @@ static void dump_iic_regs(const char* header, struct ibm_iic_private* dev) #endif /* Bus timings (in ns) for bit-banging */ -static struct i2c_timings { +static struct ibm_iic_timings { unsigned int hd_sta; unsigned int su_sto; unsigned int low; @@ -241,7 +241,7 @@ static int iic_dc_wait(volatile struct iic_regs __iomem *iic, u8 mask) static int iic_smbus_quick(struct ibm_iic_private* dev, const struct i2c_msg* p) { volatile struct iic_regs __iomem *iic = dev->vaddr; - const struct i2c_timings* t = &timings[dev->fast_mode ? 1 : 0]; + const struct ibm_iic_timings *t = &timings[dev->fast_mode ? 1 : 0]; u8 mask, v, sda; int i, res; @@ -269,7 +269,7 @@ static int iic_smbus_quick(struct ibm_iic_private* dev, const struct i2c_msg* p) ndelay(t->hd_sta); /* Send address */ - v = (u8)((p->addr << 1) | ((p->flags & I2C_M_RD) ? 1 : 0)); + v = i2c_8bit_addr_from_msg(p); for (i = 0, mask = 0x80; i < 8; ++i, mask >>= 1){ out_8(&iic->directcntl, sda); ndelay(t->low / 2); diff --git a/drivers/i2c/busses/i2c-img-scb.c b/drivers/i2c/busses/i2c-img-scb.c index 3795fe1..ea20425 100644 --- a/drivers/i2c/busses/i2c-img-scb.c +++ b/drivers/i2c/busses/i2c-img-scb.c @@ -151,10 +151,11 @@ #define INT_FIFO_EMPTYING BIT(12) #define INT_TRANSACTION_DONE BIT(15) #define INT_SLAVE_EVENT BIT(16) +#define INT_MASTER_HALTED BIT(17) #define INT_TIMING BIT(18) +#define INT_STOP_DETECTED BIT(19) #define INT_FIFO_FULL_FILLING (INT_FIFO_FULL | INT_FIFO_FILLING) -#define INT_FIFO_EMPTY_EMPTYING (INT_FIFO_EMPTY | INT_FIFO_EMPTYING) /* Level interrupts need clearing after handling instead of before */ #define INT_LEVEL 0x01e00 @@ -177,7 +178,8 @@ INT_FIFO_FULL | \ INT_FIFO_FILLING | \ INT_FIFO_EMPTY | \ - INT_FIFO_EMPTYING) + INT_MASTER_HALTED | \ + INT_STOP_DETECTED) #define INT_ENABLE_MASK_WAITSTOP (INT_SLAVE_EVENT | \ INT_ADDR_ACK_ERR | \ @@ -511,7 +513,17 @@ static void img_i2c_soft_reset(struct img_i2c *i2c) SCB_CONTROL_CLK_ENABLE | SCB_CONTROL_SOFT_RESET); } -/* enable or release transaction halt for control of repeated starts */ +/* + * Enable or release transaction halt for control of repeated starts. + * In version 3.3 of the IP when transaction halt is set, an interrupt + * will be generated after each byte of a transfer instead of after + * every transfer but before the stop bit. + * Due to this behaviour we have to be careful that every time we + * release the transaction halt we have to re-enable it straight away + * so that we only process a single byte, not doing so will result in + * all remaining bytes been processed and a stop bit being issued, + * which will prevent us having a repeated start. + */ static void img_i2c_transaction_halt(struct img_i2c *i2c, bool t_halt) { u32 val; @@ -580,7 +592,6 @@ static void img_i2c_read(struct img_i2c *i2c) img_i2c_writel(i2c, SCB_READ_ADDR_REG, i2c->msg.addr); img_i2c_writel(i2c, SCB_READ_COUNT_REG, i2c->msg.len); - img_i2c_transaction_halt(i2c, false); mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); } @@ -594,7 +605,6 @@ static void img_i2c_write(struct img_i2c *i2c) img_i2c_writel(i2c, SCB_WRITE_ADDR_REG, i2c->msg.addr); img_i2c_writel(i2c, SCB_WRITE_COUNT_REG, i2c->msg.len); - img_i2c_transaction_halt(i2c, false); mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); img_i2c_write_fifo(i2c); @@ -741,16 +751,16 @@ static unsigned int img_i2c_atomic(struct img_i2c *i2c, switch (i2c->at_cur_cmd) { case CMD_GEN_START: next_cmd = CMD_GEN_DATA; - next_data = (i2c->msg.addr << 1); - if (i2c->msg.flags & I2C_M_RD) - next_data |= 0x1; + next_data = i2c_8bit_addr_from_msg(&i2c->msg); break; case CMD_GEN_DATA: if (i2c->line_status & LINESTAT_INPUT_HELD_V) next_cmd = CMD_RET_ACK; break; case CMD_RET_ACK: - if (i2c->line_status & LINESTAT_ACK_DET) { + if (i2c->line_status & LINESTAT_ACK_DET || + (i2c->line_status & LINESTAT_NACK_DET && + i2c->msg.flags & I2C_M_IGNORE_NAK)) { if (i2c->msg.len == 0) { next_cmd = CMD_GEN_STOP; } else if (i2c->msg.flags & I2C_M_RD) { @@ -858,34 +868,42 @@ static unsigned int img_i2c_auto(struct img_i2c *i2c, /* Enable transaction halt on start bit */ if (!i2c->last_msg && line_status & LINESTAT_START_BIT_DET) { - img_i2c_transaction_halt(i2c, true); + img_i2c_transaction_halt(i2c, !i2c->last_msg); /* we're no longer interested in the slave event */ i2c->int_enable &= ~INT_SLAVE_EVENT; } mod_timer(&i2c->check_timer, jiffies + msecs_to_jiffies(1)); + if (int_status & INT_STOP_DETECTED) { + /* Drain remaining data in FIFO and complete transaction */ + if (i2c->msg.flags & I2C_M_RD) + img_i2c_read_fifo(i2c); + return ISR_COMPLETE(0); + } + if (i2c->msg.flags & I2C_M_RD) { - if (int_status & INT_FIFO_FULL_FILLING) { + if (int_status & (INT_FIFO_FULL_FILLING | INT_MASTER_HALTED)) { img_i2c_read_fifo(i2c); if (i2c->msg.len == 0) return ISR_WAITSTOP; } } else { - if (int_status & INT_FIFO_EMPTY_EMPTYING) { - /* - * The write fifo empty indicates that we're in the - * last byte so it's safe to start a new write - * transaction without losing any bytes from the - * previous one. - * see 2.3.7 Repeated Start Transactions. - */ + if (int_status & (INT_FIFO_EMPTY | INT_MASTER_HALTED)) { if ((int_status & INT_FIFO_EMPTY) && i2c->msg.len == 0) return ISR_WAITSTOP; img_i2c_write_fifo(i2c); } } + if (int_status & INT_MASTER_HALTED) { + /* + * Release and then enable transaction halt, to + * allow only a single byte to proceed. + */ + img_i2c_transaction_halt(i2c, false); + img_i2c_transaction_halt(i2c, !i2c->last_msg); + } return 0; } @@ -1017,20 +1035,23 @@ static int img_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, return -EIO; for (i = 0; i < num; i++) { - if (likely(msgs[i].len)) - continue; /* * 0 byte reads are not possible because the slave could try * and pull the data line low, preventing a stop bit. */ - if (unlikely(msgs[i].flags & I2C_M_RD)) + if (!msgs[i].len && msgs[i].flags & I2C_M_RD) return -EIO; /* * 0 byte writes are possible and used for probing, but we * cannot do them in automatic mode, so use atomic mode * instead. + * + * Also, the I2C_M_IGNORE_NAK mode can only be implemented + * in atomic mode. */ - atomic = true; + if (!msgs[i].len || + (msgs[i].flags & I2C_M_IGNORE_NAK)) + atomic = true; } ret = clk_prepare_enable(i2c->scb_clk); @@ -1069,12 +1090,31 @@ static int img_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, img_i2c_writel(i2c, SCB_INT_CLEAR_REG, ~0); img_i2c_writel(i2c, SCB_CLEAR_REG, ~0); - if (atomic) + if (atomic) { img_i2c_atomic_start(i2c); - else if (msg->flags & I2C_M_RD) - img_i2c_read(i2c); - else - img_i2c_write(i2c); + } else { + /* + * Enable transaction halt if not the last message in + * the queue so that we can control repeated starts. + */ + img_i2c_transaction_halt(i2c, !i2c->last_msg); + + if (msg->flags & I2C_M_RD) + img_i2c_read(i2c); + else + img_i2c_write(i2c); + + /* + * Release and then enable transaction halt, to + * allow only a single byte to proceed. + * This doesn't have an effect on the initial transfer + * but will allow the following transfers to start + * processing if the previous transfer was marked as + * complete while the i2c block was halted. + */ + img_i2c_transaction_halt(i2c, false); + img_i2c_transaction_halt(i2c, !i2c->last_msg); + } spin_unlock_irqrestore(&i2c->lock, flags); time_left = wait_for_completion_timeout(&i2c->msg_complete, diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index d4d8536..1844bc9 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -29,9 +29,6 @@ * */ -/** Includes ******************************************************************* -*******************************************************************************/ - #include #include #include @@ -53,12 +50,10 @@ #include #include #include +#include #include #include -/** Defines ******************************************************************** -*******************************************************************************/ - /* This will be the driver name the kernel reports */ #define DRIVER_NAME "imx-i2c" @@ -120,8 +115,7 @@ #define I2CR_IEN_OPCODE_0 0x0 #define I2CR_IEN_OPCODE_1 I2CR_IEN -/** Variables ****************************************************************** -*******************************************************************************/ +#define I2C_PM_TIMEOUT 10 /* ms */ /* * sorted list of clock divider, register value pairs @@ -218,7 +212,7 @@ struct imx_i2c_struct { struct imx_i2c_dma *dma; }; -static const struct imx_i2c_hwdata imx1_i2c_hwdata = { +static const struct imx_i2c_hwdata imx1_i2c_hwdata = { .devtype = IMX1_I2C, .regshift = IMX_I2C_REGSHIFT, .clk_div = imx_i2c_clk_div, @@ -228,7 +222,7 @@ static const struct imx_i2c_hwdata imx1_i2c_hwdata = { }; -static const struct imx_i2c_hwdata imx21_i2c_hwdata = { +static const struct imx_i2c_hwdata imx21_i2c_hwdata = { .devtype = IMX21_I2C, .regshift = IMX_I2C_REGSHIFT, .clk_div = imx_i2c_clk_div, @@ -346,7 +340,7 @@ fail_tx: dma_release_channel(dma->chan_tx); fail_al: devm_kfree(dev, dma); - dev_info(dev, "can't use DMA\n"); + dev_info(dev, "can't use DMA, using PIO instead.\n"); } static void i2c_imx_dma_callback(void *arg) @@ -393,6 +387,7 @@ static int i2c_imx_dma_xfer(struct imx_i2c_struct *i2c_imx, return 0; err_submit: + dmaengine_terminate_all(dma->chan_using); err_desc: dma_unmap_single(chan_dev, dma->dma_buf, dma->dma_len, dma->dma_data_dir); @@ -416,9 +411,6 @@ static void i2c_imx_dma_free(struct imx_i2c_struct *i2c_imx) dma->chan_using = NULL; } -/** Functions for IMX I2C adapter driver *************************************** -*******************************************************************************/ - static int i2c_imx_bus_busy(struct imx_i2c_struct *i2c_imx, int for_busy) { unsigned long orig_jiffies = jiffies; @@ -527,16 +519,13 @@ static int i2c_imx_start(struct imx_i2c_struct *i2c_imx) i2c_imx_set_clk(i2c_imx); - result = clk_prepare_enable(i2c_imx->clk); - if (result) - return result; imx_i2c_write_reg(i2c_imx->ifdr, i2c_imx, IMX_I2C_IFDR); /* Enable I2C controller */ imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx, IMX_I2C_I2SR); imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode, i2c_imx, IMX_I2C_I2CR); /* Wait controller to be stable */ - udelay(50); + usleep_range(50, 150); /* Start I2C transaction */ temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); @@ -582,7 +571,6 @@ static void i2c_imx_stop(struct imx_i2c_struct *i2c_imx) /* Disable I2C controller */ temp = i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN, imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - clk_disable_unprepare(i2c_imx->clk); } static irqreturn_t i2c_imx_isr(int irq, void *dev_id) @@ -883,7 +871,7 @@ static int i2c_imx_read(struct imx_i2c_struct *i2c_imx, struct i2c_msg *msgs, bo if ((!i) && block_data) msgs->buf[0] = len; else - msgs->buf[i] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); + msgs->buf[i] = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2DR); dev_dbg(&i2c_imx->adapter.dev, "<%s> read byte: B%d=0x%X\n", __func__, i, msgs->buf[i]); @@ -901,6 +889,10 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__); + result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent); + if (result < 0) + goto out; + /* Start I2C transfer */ result = i2c_imx_start(i2c_imx); if (result) { @@ -924,7 +916,7 @@ static int i2c_imx_xfer(struct i2c_adapter *adapter, temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR); temp |= I2CR_RSTA; imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR); - result = i2c_imx_bus_busy(i2c_imx, 1); + result = i2c_imx_bus_busy(i2c_imx, 1); if (result) goto fail0; } @@ -964,6 +956,10 @@ fail0: /* Stop I2C transfer */ i2c_imx_stop(i2c_imx); + pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent); + pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent); + +out: dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__, (result < 0) ? "error" : "success msg", (result < 0) ? result : num); @@ -997,10 +993,8 @@ static void i2c_imx_init_recovery_info(struct imx_i2c_struct *i2c_imx, PINCTRL_STATE_DEFAULT); i2c_imx->pinctrl_pins_gpio = pinctrl_lookup_state(i2c_imx->pinctrl, "gpio"); - rinfo->sda_gpio = of_get_named_gpio_flags(pdev->dev.of_node, - "sda-gpios", 0, NULL); - rinfo->scl_gpio = of_get_named_gpio_flags(pdev->dev.of_node, - "scl-gpios", 0, NULL); + rinfo->sda_gpio = of_get_named_gpio(pdev->dev.of_node, "sda-gpios", 0); + rinfo->scl_gpio = of_get_named_gpio(pdev->dev.of_node, "scl-gpios", 0); if (!gpio_is_valid(rinfo->sda_gpio) || !gpio_is_valid(rinfo->scl_gpio) || @@ -1083,7 +1077,7 @@ static int i2c_imx_probe(struct platform_device *pdev) ret = clk_prepare_enable(i2c_imx->clk); if (ret) { - dev_err(&pdev->dev, "can't enable I2C clock\n"); + dev_err(&pdev->dev, "can't enable I2C clock, ret=%d\n", ret); return ret; } @@ -1107,6 +1101,18 @@ static int i2c_imx_probe(struct platform_device *pdev) /* Set up adapter data */ i2c_set_adapdata(&i2c_imx->adapter, i2c_imx); + /* Set up platform driver data */ + platform_set_drvdata(pdev, i2c_imx); + + pm_runtime_set_autosuspend_delay(&pdev->dev, I2C_PM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + goto rpm_disable; + /* Set up clock divider */ i2c_imx->bitrate = IMX_I2C_BIT_RATE; ret = of_property_read_u32(pdev->dev.of_node, @@ -1125,12 +1131,11 @@ static int i2c_imx_probe(struct platform_device *pdev) ret = i2c_add_numbered_adapter(&i2c_imx->adapter); if (ret < 0) { dev_err(&pdev->dev, "registration failed\n"); - goto clk_disable; + goto rpm_disable; } - /* Set up platform driver data */ - platform_set_drvdata(pdev, i2c_imx); - clk_disable_unprepare(i2c_imx->clk); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); dev_dbg(&i2c_imx->adapter.dev, "claimed irq %d\n", irq); dev_dbg(&i2c_imx->adapter.dev, "device resources: %pR\n", res); @@ -1143,6 +1148,12 @@ static int i2c_imx_probe(struct platform_device *pdev) return 0; /* Return OK */ +rpm_disable: + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + clk_disable: clk_disable_unprepare(i2c_imx->clk); return ret; @@ -1151,6 +1162,11 @@ clk_disable: static int i2c_imx_remove(struct platform_device *pdev) { struct imx_i2c_struct *i2c_imx = platform_get_drvdata(pdev); + int ret; + + ret = pm_runtime_get_sync(&pdev->dev); + if (ret < 0) + return ret; /* remove adapter */ dev_dbg(&i2c_imx->adapter.dev, "adapter removed\n"); @@ -1165,17 +1181,54 @@ static int i2c_imx_remove(struct platform_device *pdev) imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2CR); imx_i2c_write_reg(0, i2c_imx, IMX_I2C_I2SR); + clk_disable_unprepare(i2c_imx->clk); + + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int i2c_imx_runtime_suspend(struct device *dev) +{ + struct imx_i2c_struct *i2c_imx = dev_get_drvdata(dev); + + clk_disable_unprepare(i2c_imx->clk); + return 0; } +static int i2c_imx_runtime_resume(struct device *dev) +{ + struct imx_i2c_struct *i2c_imx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2c_imx->clk); + if (ret) + dev_err(dev, "can't enable I2C clock, ret=%d\n", ret); + + return ret; +} + +static const struct dev_pm_ops i2c_imx_pm_ops = { + SET_RUNTIME_PM_OPS(i2c_imx_runtime_suspend, + i2c_imx_runtime_resume, NULL) +}; +#define I2C_IMX_PM_OPS (&i2c_imx_pm_ops) +#else +#define I2C_IMX_PM_OPS NULL +#endif /* CONFIG_PM */ + static struct platform_driver i2c_imx_driver = { .probe = i2c_imx_probe, .remove = i2c_imx_remove, - .driver = { - .name = DRIVER_NAME, + .driver = { + .name = DRIVER_NAME, + .pm = I2C_IMX_PM_OPS, .of_match_table = i2c_imx_dt_ids, }, - .id_table = imx_i2c_devtype, + .id_table = imx_i2c_devtype, }; static int __init i2c_adap_imx_init(void) diff --git a/drivers/i2c/busses/i2c-iop3xx.c b/drivers/i2c/busses/i2c-iop3xx.c index 72d6161..85cbe4b 100644 --- a/drivers/i2c/busses/i2c-iop3xx.c +++ b/drivers/i2c/busses/i2c-iop3xx.c @@ -50,10 +50,7 @@ iic_cook_addr(struct i2c_msg *msg) { unsigned char addr; - addr = (msg->addr << 1); - - if (msg->flags & I2C_M_RD) - addr |= 1; + addr = i2c_8bit_addr_from_msg(msg); return addr; } diff --git a/drivers/i2c/busses/i2c-ismt.c b/drivers/i2c/busses/i2c-ismt.c index 7ba795b..1c87077 100644 --- a/drivers/i2c/busses/i2c-ismt.c +++ b/drivers/i2c/busses/i2c-ismt.c @@ -75,6 +75,7 @@ /* PCI DIDs for the Intel SMBus Message Transport (SMT) Devices */ #define PCI_DEVICE_ID_INTEL_S1200_SMT0 0x0c59 #define PCI_DEVICE_ID_INTEL_S1200_SMT1 0x0c5a +#define PCI_DEVICE_ID_INTEL_DNV_SMT 0x19ac #define PCI_DEVICE_ID_INTEL_AVOTON_SMT 0x1f15 #define ISMT_DESC_ENTRIES 2 /* number of descriptor entries */ @@ -180,6 +181,7 @@ struct ismt_priv { static const struct pci_device_id ismt_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_S1200_SMT0) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_S1200_SMT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DNV_SMT) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_AVOTON_SMT) }, { 0, } }; diff --git a/drivers/i2c/busses/i2c-jz4780.c b/drivers/i2c/busses/i2c-jz4780.c index f325663..ba14a86 100644 --- a/drivers/i2c/busses/i2c-jz4780.c +++ b/drivers/i2c/busses/i2c-jz4780.c @@ -771,11 +771,16 @@ static int jz4780_i2c_probe(struct platform_device *pdev) ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &clk_freq); if (ret) { - dev_err(&pdev->dev, "clock-frequency not specified in DT"); + dev_err(&pdev->dev, "clock-frequency not specified in DT\n"); goto err; } i2c->speed = clk_freq / 1000; + if (i2c->speed == 0) { + ret = -EINVAL; + dev_err(&pdev->dev, "clock-frequency minimum is 1000\n"); + goto err; + } jz4780_i2c_set_speed(i2c); dev_info(&pdev->dev, "Bus frequency is %d KHz\n", i2c->speed); diff --git a/drivers/i2c/busses/i2c-lpc2k.c b/drivers/i2c/busses/i2c-lpc2k.c index 8560a13..586a152 100644 --- a/drivers/i2c/busses/i2c-lpc2k.c +++ b/drivers/i2c/busses/i2c-lpc2k.c @@ -133,9 +133,7 @@ static void i2c_lpc2k_pump_msg(struct lpc2k_i2c *i2c) case M_START: case M_REPSTART: /* Start bit was just sent out, send out addr and dir */ - data = i2c->msg->addr << 1; - if (i2c->msg->flags & I2C_M_RD) - data |= 1; + data = i2c_8bit_addr_from_msg(i2c->msg); writel(data, i2c->base + LPC24XX_I2DAT); writel(LPC24XX_STA, i2c->base + LPC24XX_I2CONCLR); diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c index 9b86716..d9373e6 100644 --- a/drivers/i2c/busses/i2c-mt65xx.c +++ b/drivers/i2c/busses/i2c-mt65xx.c @@ -60,6 +60,7 @@ #define I2C_DMA_INT_FLAG_NONE 0x0000 #define I2C_DMA_CLR_FLAG 0x0000 #define I2C_DMA_HARD_RST 0x0002 +#define I2C_DMA_4G_MODE 0x0001 #define I2C_DEFAULT_SPEED 100000 /* hz */ #define MAX_FS_MODE_SPEED 400000 @@ -88,6 +89,8 @@ enum DMA_REGS_OFFSET { OFFSET_RX_MEM_ADDR = 0x20, OFFSET_TX_LEN = 0x24, OFFSET_RX_LEN = 0x28, + OFFSET_TX_4G_MODE = 0x54, + OFFSET_RX_4G_MODE = 0x58, }; enum i2c_trans_st_rs { @@ -132,6 +135,8 @@ struct mtk_i2c_compatible { unsigned char pmic_i2c: 1; unsigned char dcm: 1; unsigned char auto_restart: 1; + unsigned char aux_len_reg: 1; + unsigned char support_33bits: 1; }; struct mtk_i2c { @@ -153,6 +158,8 @@ struct mtk_i2c { enum mtk_trans_op op; u16 timing_reg; u16 high_speed_reg; + unsigned char auto_restart; + bool ignore_restart_irq; const struct mtk_i2c_compatible *dev_comp; }; @@ -178,6 +185,8 @@ static const struct mtk_i2c_compatible mt6577_compat = { .pmic_i2c = 0, .dcm = 1, .auto_restart = 0, + .aux_len_reg = 0, + .support_33bits = 0, }; static const struct mtk_i2c_compatible mt6589_compat = { @@ -185,6 +194,8 @@ static const struct mtk_i2c_compatible mt6589_compat = { .pmic_i2c = 1, .dcm = 0, .auto_restart = 0, + .aux_len_reg = 0, + .support_33bits = 0, }; static const struct mtk_i2c_compatible mt8173_compat = { @@ -192,6 +203,8 @@ static const struct mtk_i2c_compatible mt8173_compat = { .pmic_i2c = 0, .dcm = 1, .auto_restart = 1, + .aux_len_reg = 1, + .support_33bits = 1, }; static const struct of_device_id mtk_i2c_of_match[] = { @@ -360,6 +373,11 @@ static int mtk_i2c_set_speed(struct mtk_i2c *i2c, unsigned int parent_clk, return 0; } +static inline u32 mtk_i2c_set_4g_mode(dma_addr_t addr) +{ + return (addr & BIT_ULL(32)) ? I2C_DMA_4G_MODE : I2C_DMA_CLR_FLAG; +} + static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, int num, int left_num) { @@ -367,13 +385,14 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, u16 start_reg; u16 control_reg; u16 restart_flag = 0; + u32 reg_4g_mode; dma_addr_t rpaddr = 0; dma_addr_t wpaddr = 0; int ret; i2c->irq_stat = 0; - if (i2c->dev_comp->auto_restart) + if (i2c->auto_restart) restart_flag = I2C_RS_TRANSFER; reinit_completion(&i2c->msg_complete); @@ -394,10 +413,7 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, else writew(I2C_FS_START_CON, i2c->base + OFFSET_EXT_CONF); - addr_reg = msgs->addr << 1; - if (i2c->op == I2C_MASTER_RD) - addr_reg |= 0x1; - + addr_reg = i2c_8bit_addr_from_msg(msgs); writew(addr_reg, i2c->base + OFFSET_SLAVE_ADDR); /* Clear interrupt status */ @@ -411,8 +427,14 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, /* Set transfer and transaction len */ if (i2c->op == I2C_MASTER_WRRD) { - writew(msgs->len | ((msgs + 1)->len) << 8, - i2c->base + OFFSET_TRANSFER_LEN); + if (i2c->dev_comp->aux_len_reg) { + writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN); + writew((msgs + 1)->len, i2c->base + + OFFSET_TRANSFER_LEN_AUX); + } else { + writew(msgs->len | ((msgs + 1)->len) << 8, + i2c->base + OFFSET_TRANSFER_LEN); + } writew(I2C_WRRD_TRANAC_VALUE, i2c->base + OFFSET_TRANSAC_LEN); } else { writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN); @@ -427,6 +449,12 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, msgs->len, DMA_FROM_DEVICE); if (dma_mapping_error(i2c->dev, rpaddr)) return -ENOMEM; + + if (i2c->dev_comp->support_33bits) { + reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr); + writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE); + } + writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR); writel(msgs->len, i2c->pdmabase + OFFSET_RX_LEN); } else if (i2c->op == I2C_MASTER_WR) { @@ -436,6 +464,12 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, msgs->len, DMA_TO_DEVICE); if (dma_mapping_error(i2c->dev, wpaddr)) return -ENOMEM; + + if (i2c->dev_comp->support_33bits) { + reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr); + writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE); + } + writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR); writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN); } else { @@ -453,6 +487,15 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, msgs->len, DMA_TO_DEVICE); return -ENOMEM; } + + if (i2c->dev_comp->support_33bits) { + reg_4g_mode = mtk_i2c_set_4g_mode(wpaddr); + writel(reg_4g_mode, i2c->pdmabase + OFFSET_TX_4G_MODE); + + reg_4g_mode = mtk_i2c_set_4g_mode(rpaddr); + writel(reg_4g_mode, i2c->pdmabase + OFFSET_RX_4G_MODE); + } + writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR); writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR); writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN); @@ -461,7 +504,7 @@ static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs, writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN); - if (!i2c->dev_comp->auto_restart) { + if (!i2c->auto_restart) { start_reg = I2C_TRANSAC_START; } else { start_reg = I2C_TRANSAC_START | I2C_RS_MUL_TRIG; @@ -518,6 +561,24 @@ static int mtk_i2c_transfer(struct i2c_adapter *adap, if (ret) return ret; + i2c->auto_restart = i2c->dev_comp->auto_restart; + + /* checking if we can skip restart and optimize using WRRD mode */ + if (i2c->auto_restart && num == 2) { + if (!(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD) && + msgs[0].addr == msgs[1].addr) { + i2c->auto_restart = 0; + } + } + + if (i2c->auto_restart && num >= 2 && i2c->speed_hz > MAX_FS_MODE_SPEED) + /* ignore the first restart irq after the master code, + * otherwise the first transfer will be discarded. + */ + i2c->ignore_restart_irq = true; + else + i2c->ignore_restart_irq = false; + while (left_num--) { if (!msgs->buf) { dev_dbg(i2c->dev, "data buffer is NULL.\n"); @@ -530,7 +591,7 @@ static int mtk_i2c_transfer(struct i2c_adapter *adap, else i2c->op = I2C_MASTER_WR; - if (!i2c->dev_comp->auto_restart) { + if (!i2c->auto_restart) { if (num > 1) { /* combined two messages into one transaction */ i2c->op = I2C_MASTER_WRRD; @@ -559,7 +620,7 @@ static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id) u16 restart_flag = 0; u16 intr_stat; - if (i2c->dev_comp->auto_restart) + if (i2c->auto_restart) restart_flag = I2C_RS_TRANSFER; intr_stat = readw(i2c->base + OFFSET_INTR_STAT); @@ -571,8 +632,16 @@ static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id) * i2c->irq_stat need keep the two interrupt value. */ i2c->irq_stat |= intr_stat; - if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag)) - complete(&i2c->msg_complete); + + if (i2c->ignore_restart_irq && (i2c->irq_stat & restart_flag)) { + i2c->ignore_restart_irq = false; + i2c->irq_stat = 0; + writew(I2C_RS_MUL_CNFG | I2C_RS_MUL_TRIG | I2C_TRANSAC_START, + i2c->base + OFFSET_START); + } else { + if (i2c->irq_stat & (I2C_TRANSAC_COMP | restart_flag)) + complete(&i2c->msg_complete); + } return IRQ_HANDLED; } @@ -691,6 +760,14 @@ static int mtk_i2c_probe(struct platform_device *pdev) return -EINVAL; } + if (i2c->dev_comp->support_33bits) { + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(33)); + if (ret) { + dev_err(&pdev->dev, "dma_set_mask return error.\n"); + return ret; + } + } + ret = mtk_i2c_clock_enable(i2c); if (ret) { dev_err(&pdev->dev, "clock enable failed!\n"); diff --git a/drivers/i2c/busses/i2c-mv64xxx.c b/drivers/i2c/busses/i2c-mv64xxx.c index 43207f5..b4dec08 100644 --- a/drivers/i2c/busses/i2c-mv64xxx.c +++ b/drivers/i2c/busses/i2c-mv64xxx.c @@ -134,9 +134,7 @@ struct mv64xxx_i2c_data { int rc; u32 freq_m; u32 freq_n; -#if defined(CONFIG_HAVE_CLK) struct clk *clk; -#endif wait_queue_head_t waitq; spinlock_t lock; struct i2c_msg *msg; @@ -757,7 +755,6 @@ static const struct of_device_id mv64xxx_i2c_of_match_table[] = { MODULE_DEVICE_TABLE(of, mv64xxx_i2c_of_match_table); #ifdef CONFIG_OF -#ifdef CONFIG_HAVE_CLK static int mv64xxx_calc_freq(struct mv64xxx_i2c_data *drv_data, const int tclk, const int n, const int m) @@ -791,25 +788,20 @@ mv64xxx_find_baud_factors(struct mv64xxx_i2c_data *drv_data, return false; return true; } -#endif /* CONFIG_HAVE_CLK */ static int mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, struct device *dev) { - /* CLK is mandatory when using DT to describe the i2c bus. We - * need to know tclk in order to calculate bus clock - * factors. - */ -#if !defined(CONFIG_HAVE_CLK) - /* Have OF but no CLK */ - return -ENODEV; -#else const struct of_device_id *device; struct device_node *np = dev->of_node; u32 bus_freq, tclk; int rc = 0; + /* CLK is mandatory when using DT to describe the i2c bus. We + * need to know tclk in order to calculate bus clock + * factors. + */ if (IS_ERR(drv_data->clk)) { rc = -ENODEV; goto out; @@ -869,7 +861,6 @@ mv64xxx_of_config(struct mv64xxx_i2c_data *drv_data, out: return rc; -#endif } #else /* CONFIG_OF */ static int @@ -907,14 +898,13 @@ mv64xxx_i2c_probe(struct platform_device *pd) init_waitqueue_head(&drv_data->waitq); spin_lock_init(&drv_data->lock); -#if defined(CONFIG_HAVE_CLK) /* Not all platforms have a clk */ drv_data->clk = devm_clk_get(&pd->dev, NULL); - if (!IS_ERR(drv_data->clk)) { - clk_prepare(drv_data->clk); - clk_enable(drv_data->clk); - } -#endif + if (IS_ERR(drv_data->clk) && PTR_ERR(drv_data->clk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + if (!IS_ERR(drv_data->clk)) + clk_prepare_enable(drv_data->clk); + if (pdata) { drv_data->freq_m = pdata->freq_m; drv_data->freq_n = pdata->freq_n; @@ -964,13 +954,10 @@ exit_reset: if (!IS_ERR_OR_NULL(drv_data->rstc)) reset_control_assert(drv_data->rstc); exit_clk: -#if defined(CONFIG_HAVE_CLK) /* Not all platforms have a clk */ - if (!IS_ERR(drv_data->clk)) { - clk_disable(drv_data->clk); - clk_unprepare(drv_data->clk); - } -#endif + if (!IS_ERR(drv_data->clk)) + clk_disable_unprepare(drv_data->clk); + return rc; } @@ -983,13 +970,9 @@ mv64xxx_i2c_remove(struct platform_device *dev) free_irq(drv_data->irq, drv_data); if (!IS_ERR_OR_NULL(drv_data->rstc)) reset_control_assert(drv_data->rstc); -#if defined(CONFIG_HAVE_CLK) /* Not all platforms have a clk */ - if (!IS_ERR(drv_data->clk)) { - clk_disable(drv_data->clk); - clk_unprepare(drv_data->clk); - } -#endif + if (!IS_ERR(drv_data->clk)) + clk_disable_unprepare(drv_data->clk); return 0; } diff --git a/drivers/i2c/busses/i2c-nforce2.c b/drivers/i2c/busses/i2c-nforce2.c index 70b3c91..42fcc94 100644 --- a/drivers/i2c/busses/i2c-nforce2.c +++ b/drivers/i2c/busses/i2c-nforce2.c @@ -127,7 +127,7 @@ static struct pci_driver nforce2_driver; /* For multiplexing support, we need a global reference to the 1st SMBus channel */ -#if defined CONFIG_I2C_NFORCE2_S4985 || defined CONFIG_I2C_NFORCE2_S4985_MODULE +#if IS_ENABLED(CONFIG_I2C_NFORCE2_S4985) struct i2c_adapter *nforce2_smbus; EXPORT_SYMBOL_GPL(nforce2_smbus); diff --git a/drivers/i2c/busses/i2c-ocores.c b/drivers/i2c/busses/i2c-ocores.c index 11b7b87..dfa7a4b 100644 --- a/drivers/i2c/busses/i2c-ocores.c +++ b/drivers/i2c/busses/i2c-ocores.c @@ -178,10 +178,7 @@ static void ocores_process(struct ocores_i2c *i2c) if (i2c->nmsgs) { /* end? */ /* send start? */ if (!(msg->flags & I2C_M_NOSTART)) { - u8 addr = (msg->addr << 1); - - if (msg->flags & I2C_M_RD) - addr |= 1; + u8 addr = i2c_8bit_addr_from_msg(msg); i2c->state = STATE_START; diff --git a/drivers/i2c/busses/i2c-octeon.c b/drivers/i2c/busses/i2c-octeon.c index 32914ab..30ae351 100644 --- a/drivers/i2c/busses/i2c-octeon.c +++ b/drivers/i2c/busses/i2c-octeon.c @@ -2,7 +2,7 @@ * (C) Copyright 2009-2010 * Nokia Siemens Networks, michael.lawnick.ext@nsn.com * - * Portions Copyright (C) 2010, 2011 Cavium Networks, Inc. + * Portions Copyright (C) 2010 - 2016 Cavium, Inc. * * This is a driver for the i2c adapter in Cavium Networks' OCTEON processors. * @@ -11,6 +11,7 @@ * warranty of any kind, whether express or implied. */ +#include #include #include #include @@ -26,63 +27,125 @@ #define DRV_NAME "i2c-octeon" -/* The previous out-of-tree version was implicitly version 1.0. */ -#define DRV_VERSION "2.0" - -/* register offsets */ -#define SW_TWSI 0x00 -#define TWSI_INT 0x10 +/* Register offsets */ +#define SW_TWSI 0x00 +#define TWSI_INT 0x10 +#define SW_TWSI_EXT 0x18 /* Controller command patterns */ -#define SW_TWSI_V 0x8000000000000000ull -#define SW_TWSI_EOP_TWSI_DATA 0x0C00000100000000ull -#define SW_TWSI_EOP_TWSI_CTL 0x0C00000200000000ull -#define SW_TWSI_EOP_TWSI_CLKCTL 0x0C00000300000000ull -#define SW_TWSI_EOP_TWSI_STAT 0x0C00000300000000ull -#define SW_TWSI_EOP_TWSI_RST 0x0C00000700000000ull -#define SW_TWSI_OP_TWSI_CLK 0x0800000000000000ull -#define SW_TWSI_R 0x0100000000000000ull +#define SW_TWSI_V BIT_ULL(63) /* Valid bit */ +#define SW_TWSI_EIA BIT_ULL(61) /* Extended internal address */ +#define SW_TWSI_R BIT_ULL(56) /* Result or read bit */ +#define SW_TWSI_SOVR BIT_ULL(55) /* Size override */ +#define SW_TWSI_SIZE_SHIFT 52 +#define SW_TWSI_ADDR_SHIFT 40 +#define SW_TWSI_IA_SHIFT 32 /* Internal address */ + +/* Controller opcode word (bits 60:57) */ +#define SW_TWSI_OP_SHIFT 57 +#define SW_TWSI_OP_7 (0ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_7_IA (1ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_10 (2ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_10_IA (3ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_TWSI_CLK (4ULL << SW_TWSI_OP_SHIFT) +#define SW_TWSI_OP_EOP (6ULL << SW_TWSI_OP_SHIFT) /* Extended opcode */ + +/* Controller extended opcode word (bits 34:32) */ +#define SW_TWSI_EOP_SHIFT 32 +#define SW_TWSI_EOP_TWSI_DATA (SW_TWSI_OP_EOP | 1ULL << SW_TWSI_EOP_SHIFT) +#define SW_TWSI_EOP_TWSI_CTL (SW_TWSI_OP_EOP | 2ULL << SW_TWSI_EOP_SHIFT) +#define SW_TWSI_EOP_TWSI_CLKCTL (SW_TWSI_OP_EOP | 3ULL << SW_TWSI_EOP_SHIFT) +#define SW_TWSI_EOP_TWSI_STAT (SW_TWSI_OP_EOP | 3ULL << SW_TWSI_EOP_SHIFT) +#define SW_TWSI_EOP_TWSI_RST (SW_TWSI_OP_EOP | 7ULL << SW_TWSI_EOP_SHIFT) /* Controller command and status bits */ -#define TWSI_CTL_CE 0x80 -#define TWSI_CTL_ENAB 0x40 -#define TWSI_CTL_STA 0x20 -#define TWSI_CTL_STP 0x10 -#define TWSI_CTL_IFLG 0x08 -#define TWSI_CTL_AAK 0x04 - -/* Some status values */ -#define STAT_START 0x08 -#define STAT_RSTART 0x10 -#define STAT_TXADDR_ACK 0x18 -#define STAT_TXDATA_ACK 0x28 -#define STAT_RXADDR_ACK 0x40 -#define STAT_RXDATA_ACK 0x50 -#define STAT_IDLE 0xF8 +#define TWSI_CTL_CE 0x80 /* High level controller enable */ +#define TWSI_CTL_ENAB 0x40 /* Bus enable */ +#define TWSI_CTL_STA 0x20 /* Master-mode start, HW clears when done */ +#define TWSI_CTL_STP 0x10 /* Master-mode stop, HW clears when done */ +#define TWSI_CTL_IFLG 0x08 /* HW event, SW writes 0 to ACK */ +#define TWSI_CTL_AAK 0x04 /* Assert ACK */ + +/* Status values */ +#define STAT_ERROR 0x00 +#define STAT_START 0x08 +#define STAT_REP_START 0x10 +#define STAT_TXADDR_ACK 0x18 +#define STAT_TXADDR_NAK 0x20 +#define STAT_TXDATA_ACK 0x28 +#define STAT_TXDATA_NAK 0x30 +#define STAT_LOST_ARB_38 0x38 +#define STAT_RXADDR_ACK 0x40 +#define STAT_RXADDR_NAK 0x48 +#define STAT_RXDATA_ACK 0x50 +#define STAT_RXDATA_NAK 0x58 +#define STAT_SLAVE_60 0x60 +#define STAT_LOST_ARB_68 0x68 +#define STAT_SLAVE_70 0x70 +#define STAT_LOST_ARB_78 0x78 +#define STAT_SLAVE_80 0x80 +#define STAT_SLAVE_88 0x88 +#define STAT_GENDATA_ACK 0x90 +#define STAT_GENDATA_NAK 0x98 +#define STAT_SLAVE_A0 0xA0 +#define STAT_SLAVE_A8 0xA8 +#define STAT_LOST_ARB_B0 0xB0 +#define STAT_SLAVE_LOST 0xB8 +#define STAT_SLAVE_NAK 0xC0 +#define STAT_SLAVE_ACK 0xC8 +#define STAT_AD2W_ACK 0xD0 +#define STAT_AD2W_NAK 0xD8 +#define STAT_IDLE 0xF8 + +/* TWSI_INT values */ +#define TWSI_INT_ST_INT BIT_ULL(0) +#define TWSI_INT_TS_INT BIT_ULL(1) +#define TWSI_INT_CORE_INT BIT_ULL(2) +#define TWSI_INT_ST_EN BIT_ULL(4) +#define TWSI_INT_TS_EN BIT_ULL(5) +#define TWSI_INT_CORE_EN BIT_ULL(6) +#define TWSI_INT_SDA_OVR BIT_ULL(8) +#define TWSI_INT_SCL_OVR BIT_ULL(9) +#define TWSI_INT_SDA BIT_ULL(10) +#define TWSI_INT_SCL BIT_ULL(11) + +#define I2C_OCTEON_EVENT_WAIT 80 /* microseconds */ struct octeon_i2c { wait_queue_head_t queue; struct i2c_adapter adap; int irq; + int hlc_irq; /* For cn7890 only */ u32 twsi_freq; int sys_freq; - resource_size_t twsi_phys; void __iomem *twsi_base; - resource_size_t regsize; struct device *dev; + bool hlc_enabled; + bool broken_irq_mode; + bool broken_irq_check; + void (*int_enable)(struct octeon_i2c *); + void (*int_disable)(struct octeon_i2c *); + void (*hlc_int_enable)(struct octeon_i2c *); + void (*hlc_int_disable)(struct octeon_i2c *); + atomic_t int_enable_cnt; + atomic_t hlc_int_enable_cnt; }; +static void octeon_i2c_writeq_flush(u64 val, void __iomem *addr) +{ + __raw_writeq(val, addr); + __raw_readq(addr); /* wait for write to land */ +} + /** - * octeon_i2c_write_sw - write an I2C core register. - * @i2c: The struct octeon_i2c. - * @eop_reg: Register selector. - * @data: Value to be written. + * octeon_i2c_reg_write - write an I2C core register + * @i2c: The struct octeon_i2c + * @eop_reg: Register selector + * @data: Value to be written * * The I2C core registers are accessed indirectly via the SW_TWSI CSR. */ -static void octeon_i2c_write_sw(struct octeon_i2c *i2c, - u64 eop_reg, - u8 data) +static void octeon_i2c_reg_write(struct octeon_i2c *i2c, u64 eop_reg, u8 data) { u64 tmp; @@ -92,16 +155,21 @@ static void octeon_i2c_write_sw(struct octeon_i2c *i2c, } while ((tmp & SW_TWSI_V) != 0); } +#define octeon_i2c_ctl_write(i2c, val) \ + octeon_i2c_reg_write(i2c, SW_TWSI_EOP_TWSI_CTL, val) +#define octeon_i2c_data_write(i2c, val) \ + octeon_i2c_reg_write(i2c, SW_TWSI_EOP_TWSI_DATA, val) + /** - * octeon_i2c_read_sw - write an I2C core register. - * @i2c: The struct octeon_i2c. - * @eop_reg: Register selector. + * octeon_i2c_reg_read - read lower bits of an I2C core register + * @i2c: The struct octeon_i2c + * @eop_reg: Register selector * * Returns the data. * * The I2C core registers are accessed indirectly via the SW_TWSI CSR. */ -static u8 octeon_i2c_read_sw(struct octeon_i2c *i2c, u64 eop_reg) +static u8 octeon_i2c_reg_read(struct octeon_i2c *i2c, u64 eop_reg) { u64 tmp; @@ -113,180 +181,695 @@ static u8 octeon_i2c_read_sw(struct octeon_i2c *i2c, u64 eop_reg) return tmp & 0xFF; } +#define octeon_i2c_ctl_read(i2c) \ + octeon_i2c_reg_read(i2c, SW_TWSI_EOP_TWSI_CTL) +#define octeon_i2c_data_read(i2c) \ + octeon_i2c_reg_read(i2c, SW_TWSI_EOP_TWSI_DATA) +#define octeon_i2c_stat_read(i2c) \ + octeon_i2c_reg_read(i2c, SW_TWSI_EOP_TWSI_STAT) + +/** + * octeon_i2c_read_int - read the TWSI_INT register + * @i2c: The struct octeon_i2c + * + * Returns the value of the register. + */ +static u64 octeon_i2c_read_int(struct octeon_i2c *i2c) +{ + return __raw_readq(i2c->twsi_base + TWSI_INT); +} + /** * octeon_i2c_write_int - write the TWSI_INT register - * @i2c: The struct octeon_i2c. - * @data: Value to be written. + * @i2c: The struct octeon_i2c + * @data: Value to be written */ static void octeon_i2c_write_int(struct octeon_i2c *i2c, u64 data) { - __raw_writeq(data, i2c->twsi_base + TWSI_INT); - __raw_readq(i2c->twsi_base + TWSI_INT); + octeon_i2c_writeq_flush(data, i2c->twsi_base + TWSI_INT); } /** - * octeon_i2c_int_enable - enable the TS interrupt. - * @i2c: The struct octeon_i2c. + * octeon_i2c_int_enable - enable the CORE interrupt + * @i2c: The struct octeon_i2c * * The interrupt will be asserted when there is non-STAT_IDLE state in * the SW_TWSI_EOP_TWSI_STAT register. */ static void octeon_i2c_int_enable(struct octeon_i2c *i2c) { - octeon_i2c_write_int(i2c, 0x40); + octeon_i2c_write_int(i2c, TWSI_INT_CORE_EN); } -/** - * octeon_i2c_int_disable - disable the TS interrupt. - * @i2c: The struct octeon_i2c. - */ +/* disable the CORE interrupt */ static void octeon_i2c_int_disable(struct octeon_i2c *i2c) { + /* clear TS/ST/IFLG events */ octeon_i2c_write_int(i2c, 0); } /** - * octeon_i2c_unblock - unblock the bus. - * @i2c: The struct octeon_i2c. + * octeon_i2c_int_enable78 - enable the CORE interrupt + * @i2c: The struct octeon_i2c * - * If there was a reset while a device was driving 0 to bus, - * bus is blocked. We toggle it free manually by some clock - * cycles and send a stop. + * The interrupt will be asserted when there is non-STAT_IDLE state in the + * SW_TWSI_EOP_TWSI_STAT register. */ -static void octeon_i2c_unblock(struct octeon_i2c *i2c) +static void octeon_i2c_int_enable78(struct octeon_i2c *i2c) { - int i; + atomic_inc_return(&i2c->int_enable_cnt); + enable_irq(i2c->irq); +} - dev_dbg(i2c->dev, "%s\n", __func__); - for (i = 0; i < 9; i++) { - octeon_i2c_write_int(i2c, 0x0); - udelay(5); - octeon_i2c_write_int(i2c, 0x200); - udelay(5); - } - octeon_i2c_write_int(i2c, 0x300); - udelay(5); - octeon_i2c_write_int(i2c, 0x100); - udelay(5); - octeon_i2c_write_int(i2c, 0x0); +static void __octeon_i2c_irq_disable(atomic_t *cnt, int irq) +{ + int count; + + /* + * The interrupt can be disabled in two places, but we only + * want to make the disable_irq_nosync() call once, so keep + * track with the atomic variable. + */ + count = atomic_dec_if_positive(cnt); + if (count >= 0) + disable_irq_nosync(irq); +} + +/* disable the CORE interrupt */ +static void octeon_i2c_int_disable78(struct octeon_i2c *i2c) +{ + __octeon_i2c_irq_disable(&i2c->int_enable_cnt, i2c->irq); } /** - * octeon_i2c_isr - the interrupt service routine. - * @int: The irq, unused. - * @dev_id: Our struct octeon_i2c. + * octeon_i2c_hlc_int_enable78 - enable the ST interrupt + * @i2c: The struct octeon_i2c + * + * The interrupt will be asserted when there is non-STAT_IDLE state in + * the SW_TWSI_EOP_TWSI_STAT register. */ +static void octeon_i2c_hlc_int_enable78(struct octeon_i2c *i2c) +{ + atomic_inc_return(&i2c->hlc_int_enable_cnt); + enable_irq(i2c->hlc_irq); +} + +/* disable the ST interrupt */ +static void octeon_i2c_hlc_int_disable78(struct octeon_i2c *i2c) +{ + __octeon_i2c_irq_disable(&i2c->hlc_int_enable_cnt, i2c->hlc_irq); +} + +/* + * Cleanup low-level state & enable high-level controller. + */ +static void octeon_i2c_hlc_enable(struct octeon_i2c *i2c) +{ + int try = 0; + u64 val; + + if (i2c->hlc_enabled) + return; + i2c->hlc_enabled = true; + + while (1) { + val = octeon_i2c_ctl_read(i2c); + if (!(val & (TWSI_CTL_STA | TWSI_CTL_STP))) + break; + + /* clear IFLG event */ + if (val & TWSI_CTL_IFLG) + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); + + if (try++ > 100) { + pr_err("%s: giving up\n", __func__); + break; + } + + /* spin until any start/stop has finished */ + udelay(10); + } + octeon_i2c_ctl_write(i2c, TWSI_CTL_CE | TWSI_CTL_AAK | TWSI_CTL_ENAB); +} + +static void octeon_i2c_hlc_disable(struct octeon_i2c *i2c) +{ + if (!i2c->hlc_enabled) + return; + + i2c->hlc_enabled = false; + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); +} + +/* interrupt service routine */ static irqreturn_t octeon_i2c_isr(int irq, void *dev_id) { struct octeon_i2c *i2c = dev_id; - octeon_i2c_int_disable(i2c); + i2c->int_disable(i2c); wake_up(&i2c->queue); return IRQ_HANDLED; } +/* HLC interrupt service routine */ +static irqreturn_t octeon_i2c_hlc_isr78(int irq, void *dev_id) +{ + struct octeon_i2c *i2c = dev_id; + + i2c->hlc_int_disable(i2c); + wake_up(&i2c->queue); -static int octeon_i2c_test_iflg(struct octeon_i2c *i2c) + return IRQ_HANDLED; +} + +static bool octeon_i2c_test_iflg(struct octeon_i2c *i2c) { - return (octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_CTL) & TWSI_CTL_IFLG) != 0; + return (octeon_i2c_ctl_read(i2c) & TWSI_CTL_IFLG); +} + +static bool octeon_i2c_test_ready(struct octeon_i2c *i2c, bool *first) +{ + if (octeon_i2c_test_iflg(i2c)) + return true; + + if (*first) { + *first = false; + return false; + } + + /* + * IRQ has signaled an event but IFLG hasn't changed. + * Sleep and retry once. + */ + usleep_range(I2C_OCTEON_EVENT_WAIT, 2 * I2C_OCTEON_EVENT_WAIT); + return octeon_i2c_test_iflg(i2c); } /** - * octeon_i2c_wait - wait for the IFLG to be set. - * @i2c: The struct octeon_i2c. + * octeon_i2c_wait - wait for the IFLG to be set + * @i2c: The struct octeon_i2c * * Returns 0 on success, otherwise a negative errno. */ static int octeon_i2c_wait(struct octeon_i2c *i2c) { - long result; + long time_left; + bool first = 1; + + /* + * Some chip revisions don't assert the irq in the interrupt + * controller. So we must poll for the IFLG change. + */ + if (i2c->broken_irq_mode) { + u64 end = get_jiffies_64() + i2c->adap.timeout; + + while (!octeon_i2c_test_iflg(i2c) && + time_before64(get_jiffies_64(), end)) + usleep_range(I2C_OCTEON_EVENT_WAIT / 2, I2C_OCTEON_EVENT_WAIT); - octeon_i2c_int_enable(i2c); + return octeon_i2c_test_iflg(i2c) ? 0 : -ETIMEDOUT; + } - result = wait_event_timeout(i2c->queue, - octeon_i2c_test_iflg(i2c), - i2c->adap.timeout); + i2c->int_enable(i2c); + time_left = wait_event_timeout(i2c->queue, octeon_i2c_test_ready(i2c, &first), + i2c->adap.timeout); + i2c->int_disable(i2c); - octeon_i2c_int_disable(i2c); + if (i2c->broken_irq_check && !time_left && + octeon_i2c_test_iflg(i2c)) { + dev_err(i2c->dev, "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = true; + return 0; + } - if (result == 0) { - dev_dbg(i2c->dev, "%s: timeout\n", __func__); + if (!time_left) return -ETIMEDOUT; - } return 0; } +static int octeon_i2c_check_status(struct octeon_i2c *i2c, int final_read) +{ + u8 stat = octeon_i2c_stat_read(i2c); + + switch (stat) { + /* Everything is fine */ + case STAT_IDLE: + case STAT_AD2W_ACK: + case STAT_RXADDR_ACK: + case STAT_TXADDR_ACK: + case STAT_TXDATA_ACK: + return 0; + + /* ACK allowed on pre-terminal bytes only */ + case STAT_RXDATA_ACK: + if (!final_read) + return 0; + return -EIO; + + /* NAK allowed on terminal byte only */ + case STAT_RXDATA_NAK: + if (final_read) + return 0; + return -EIO; + + /* Arbitration lost */ + case STAT_LOST_ARB_38: + case STAT_LOST_ARB_68: + case STAT_LOST_ARB_78: + case STAT_LOST_ARB_B0: + return -EAGAIN; + + /* Being addressed as slave, should back off & listen */ + case STAT_SLAVE_60: + case STAT_SLAVE_70: + case STAT_GENDATA_ACK: + case STAT_GENDATA_NAK: + return -EOPNOTSUPP; + + /* Core busy as slave */ + case STAT_SLAVE_80: + case STAT_SLAVE_88: + case STAT_SLAVE_A0: + case STAT_SLAVE_A8: + case STAT_SLAVE_LOST: + case STAT_SLAVE_NAK: + case STAT_SLAVE_ACK: + return -EOPNOTSUPP; + + case STAT_TXDATA_NAK: + return -EIO; + case STAT_TXADDR_NAK: + case STAT_RXADDR_NAK: + case STAT_AD2W_NAK: + return -ENXIO; + default: + dev_err(i2c->dev, "unhandled state: %d\n", stat); + return -EIO; + } +} + +static bool octeon_i2c_hlc_test_valid(struct octeon_i2c *i2c) +{ + return (__raw_readq(i2c->twsi_base + SW_TWSI) & SW_TWSI_V) == 0; +} + +static bool octeon_i2c_hlc_test_ready(struct octeon_i2c *i2c, bool *first) +{ + /* check if valid bit is cleared */ + if (octeon_i2c_hlc_test_valid(i2c)) + return true; + + if (*first) { + *first = false; + return false; + } + + /* + * IRQ has signaled an event but valid bit isn't cleared. + * Sleep and retry once. + */ + usleep_range(I2C_OCTEON_EVENT_WAIT, 2 * I2C_OCTEON_EVENT_WAIT); + return octeon_i2c_hlc_test_valid(i2c); +} + +static void octeon_i2c_hlc_int_enable(struct octeon_i2c *i2c) +{ + octeon_i2c_write_int(i2c, TWSI_INT_ST_EN); +} + +static void octeon_i2c_hlc_int_clear(struct octeon_i2c *i2c) +{ + /* clear ST/TS events, listen for neither */ + octeon_i2c_write_int(i2c, TWSI_INT_ST_INT | TWSI_INT_TS_INT); +} + /** - * octeon_i2c_start - send START to the bus. - * @i2c: The struct octeon_i2c. + * octeon_i2c_hlc_wait - wait for an HLC operation to complete + * @i2c: The struct octeon_i2c * - * Returns 0 on success, otherwise a negative errno. + * Returns 0 on success, otherwise -ETIMEDOUT. */ -static int octeon_i2c_start(struct octeon_i2c *i2c) +static int octeon_i2c_hlc_wait(struct octeon_i2c *i2c) { - u8 data; - int result; + bool first = 1; + int time_left; - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, - TWSI_CTL_ENAB | TWSI_CTL_STA); + /* + * Some cn38xx boards don't assert the irq in the interrupt + * controller. So we must poll for the valid bit change. + */ + if (i2c->broken_irq_mode) { + u64 end = get_jiffies_64() + i2c->adap.timeout; - result = octeon_i2c_wait(i2c); - if (result) { - if (octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT) == STAT_IDLE) { + while (!octeon_i2c_hlc_test_valid(i2c) && + time_before64(get_jiffies_64(), end)) + usleep_range(I2C_OCTEON_EVENT_WAIT / 2, I2C_OCTEON_EVENT_WAIT); + + return octeon_i2c_hlc_test_valid(i2c) ? 0 : -ETIMEDOUT; + } + + i2c->hlc_int_enable(i2c); + time_left = wait_event_timeout(i2c->queue, + octeon_i2c_hlc_test_ready(i2c, &first), + i2c->adap.timeout); + i2c->hlc_int_disable(i2c); + if (!time_left) + octeon_i2c_hlc_int_clear(i2c); + + if (i2c->broken_irq_check && !time_left && + octeon_i2c_hlc_test_valid(i2c)) { + dev_err(i2c->dev, "broken irq connection detected, switching to polling mode.\n"); + i2c->broken_irq_mode = true; + return 0; + } + + if (!time_left) + return -ETIMEDOUT; + return 0; +} + +/* high-level-controller pure read of up to 8 bytes */ +static int octeon_i2c_hlc_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_hlc_enable(i2c); + octeon_i2c_hlc_int_clear(i2c); + + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + octeon_i2c_writeq_flush(cmd, i2c->twsi_base + SW_TWSI); + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[0].len > 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + msgs[0].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +/* high-level-controller pure write of up to 8 bytes */ +static int octeon_i2c_hlc_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_hlc_enable(i2c); + octeon_i2c_hlc_int_clear(i2c); + + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[0].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10; + else + cmd |= SW_TWSI_OP_7; + + for (i = 0, j = msgs[0].len - 1; i < msgs[0].len && i < 4; i++, j--) + cmd |= (u64)msgs[0].buf[j] << (8 * i); + + if (msgs[0].len > 4) { + u64 ext = 0; + + for (i = 0; i < msgs[0].len - 4 && i < 4; i++, j--) + ext |= (u64)msgs[0].buf[j] << (8 * i); + octeon_i2c_writeq_flush(ext, i2c->twsi_base + SW_TWSI_EXT); + } + + octeon_i2c_writeq_flush(cmd, i2c->twsi_base + SW_TWSI); + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + ret = octeon_i2c_check_status(i2c, false); + +err: + return ret; +} + +/* high-level-controller composite write+read, msg0=addr, msg1=data */ +static int octeon_i2c_hlc_comp_read(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + int i, j, ret = 0; + u64 cmd; + + octeon_i2c_hlc_enable(i2c); + + cmd = SW_TWSI_V | SW_TWSI_R | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + u64 ext = 0; + + cmd |= SW_TWSI_EIA; + ext = (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + cmd |= (u64)msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + octeon_i2c_writeq_flush(ext, i2c->twsi_base + SW_TWSI_EXT); + } else { + cmd |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + } + + octeon_i2c_hlc_int_clear(i2c); + octeon_i2c_writeq_flush(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + + if (msgs[1].len > 4) { + cmd = __raw_readq(i2c->twsi_base + SW_TWSI_EXT); + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + msgs[1].buf[j] = (cmd >> (8 * i)) & 0xff; + } + +err: + return ret; +} + +/* high-level-controller composite write+write, m[0]len<=2, m[1]len<=8 */ +static int octeon_i2c_hlc_comp_write(struct octeon_i2c *i2c, struct i2c_msg *msgs) +{ + bool set_ext = false; + int i, j, ret = 0; + u64 cmd, ext = 0; + + octeon_i2c_hlc_enable(i2c); + + cmd = SW_TWSI_V | SW_TWSI_SOVR; + /* SIZE */ + cmd |= (u64)(msgs[1].len - 1) << SW_TWSI_SIZE_SHIFT; + /* A */ + cmd |= (u64)(msgs[0].addr & 0x7full) << SW_TWSI_ADDR_SHIFT; + + if (msgs[0].flags & I2C_M_TEN) + cmd |= SW_TWSI_OP_10_IA; + else + cmd |= SW_TWSI_OP_7_IA; + + if (msgs[0].len == 2) { + cmd |= SW_TWSI_EIA; + ext |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + set_ext = true; + cmd |= (u64)msgs[0].buf[1] << SW_TWSI_IA_SHIFT; + } else { + cmd |= (u64)msgs[0].buf[0] << SW_TWSI_IA_SHIFT; + } + + for (i = 0, j = msgs[1].len - 1; i < msgs[1].len && i < 4; i++, j--) + cmd |= (u64)msgs[1].buf[j] << (8 * i); + + if (msgs[1].len > 4) { + for (i = 0; i < msgs[1].len - 4 && i < 4; i++, j--) + ext |= (u64)msgs[1].buf[j] << (8 * i); + set_ext = true; + } + if (set_ext) + octeon_i2c_writeq_flush(ext, i2c->twsi_base + SW_TWSI_EXT); + + octeon_i2c_hlc_int_clear(i2c); + octeon_i2c_writeq_flush(cmd, i2c->twsi_base + SW_TWSI); + + ret = octeon_i2c_hlc_wait(i2c); + if (ret) + goto err; + + cmd = __raw_readq(i2c->twsi_base + SW_TWSI); + if ((cmd & SW_TWSI_R) == 0) + return -EAGAIN; + + ret = octeon_i2c_check_status(i2c, false); + +err: + return ret; +} + +/* calculate and set clock divisors */ +static void octeon_i2c_set_clock(struct octeon_i2c *i2c) +{ + int tclk, thp_base, inc, thp_idx, mdiv_idx, ndiv_idx, foscl, diff; + int thp = 0x18, mdiv = 2, ndiv = 0, delta_hz = 1000000; + + for (ndiv_idx = 0; ndiv_idx < 8 && delta_hz != 0; ndiv_idx++) { + /* + * An mdiv value of less than 2 seems to not work well + * with ds1337 RTCs, so we constrain it to larger values. + */ + for (mdiv_idx = 15; mdiv_idx >= 2 && delta_hz != 0; mdiv_idx--) { /* - * Controller refused to send start flag May - * be a client is holding SDA low - let's try - * to free it. + * For given ndiv and mdiv values check the + * two closest thp values. */ - octeon_i2c_unblock(i2c); - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, - TWSI_CTL_ENAB | TWSI_CTL_STA); + tclk = i2c->twsi_freq * (mdiv_idx + 1) * 10; + tclk *= (1 << ndiv_idx); + thp_base = (i2c->sys_freq / (tclk * 2)) - 1; + + for (inc = 0; inc <= 1; inc++) { + thp_idx = thp_base + inc; + if (thp_idx < 5 || thp_idx > 0xff) + continue; - result = octeon_i2c_wait(i2c); + foscl = i2c->sys_freq / (2 * (thp_idx + 1)); + foscl = foscl / (1 << ndiv_idx); + foscl = foscl / (mdiv_idx + 1) / 10; + diff = abs(foscl - i2c->twsi_freq); + if (diff < delta_hz) { + delta_hz = diff; + thp = thp_idx; + mdiv = mdiv_idx; + ndiv = ndiv_idx; + } + } } - if (result) - return result; } + octeon_i2c_reg_write(i2c, SW_TWSI_OP_TWSI_CLK, thp); + octeon_i2c_reg_write(i2c, SW_TWSI_EOP_TWSI_CLKCTL, (mdiv << 3) | ndiv); +} - data = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if ((data != STAT_START) && (data != STAT_RSTART)) { - dev_err(i2c->dev, "%s: bad status (0x%x)\n", __func__, data); +static int octeon_i2c_init_lowlevel(struct octeon_i2c *i2c) +{ + u8 status = 0; + int tries; + + /* reset controller */ + octeon_i2c_reg_write(i2c, SW_TWSI_EOP_TWSI_RST, 0); + + for (tries = 10; tries && status != STAT_IDLE; tries--) { + udelay(1); + status = octeon_i2c_stat_read(i2c); + if (status == STAT_IDLE) + break; + } + + if (status != STAT_IDLE) { + dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", + __func__, status); return -EIO; } + /* toggle twice to force both teardowns */ + octeon_i2c_hlc_enable(i2c); + octeon_i2c_hlc_disable(i2c); return 0; } +static int octeon_i2c_recovery(struct octeon_i2c *i2c) +{ + int ret; + + ret = i2c_recover_bus(&i2c->adap); + if (ret) + /* recover failed, try hardware re-init */ + ret = octeon_i2c_init_lowlevel(i2c); + return ret; +} + /** - * octeon_i2c_stop - send STOP to the bus. - * @i2c: The struct octeon_i2c. + * octeon_i2c_start - send START to the bus + * @i2c: The struct octeon_i2c * * Returns 0 on success, otherwise a negative errno. */ -static int octeon_i2c_stop(struct octeon_i2c *i2c) +static int octeon_i2c_start(struct octeon_i2c *i2c) { - u8 data; + int ret; + u8 stat; - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, - TWSI_CTL_ENAB | TWSI_CTL_STP); + octeon_i2c_hlc_disable(i2c); - data = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_STA); + ret = octeon_i2c_wait(i2c); + if (ret) + goto error; - if (data != STAT_IDLE) { - dev_err(i2c->dev, "%s: bad status(0x%x)\n", __func__, data); - return -EIO; - } - return 0; + stat = octeon_i2c_stat_read(i2c); + if (stat == STAT_START || stat == STAT_REP_START) + /* START successful, bail out */ + return 0; + +error: + /* START failed, try to recover */ + ret = octeon_i2c_recovery(i2c); + return (ret) ? ret : -EAGAIN; +} + +/* send STOP to the bus */ +static void octeon_i2c_stop(struct octeon_i2c *i2c) +{ + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_STP); } /** - * octeon_i2c_write - send data to the bus. - * @i2c: The struct octeon_i2c. - * @target: Target address. - * @data: Pointer to the data to be sent. - * @length: Length of the data. + * octeon_i2c_write - send data to the bus via low-level controller + * @i2c: The struct octeon_i2c + * @target: Target address + * @data: Pointer to the data to be sent + * @length: Length of the data * * The address is sent over the bus, then the data. * @@ -296,30 +879,21 @@ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, const u8 *data, int length) { int i, result; - u8 tmp; - result = octeon_i2c_start(i2c); - if (result) - return result; - - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_DATA, target << 1); - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); + octeon_i2c_data_write(i2c, target << 1); + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); result = octeon_i2c_wait(i2c); if (result) return result; for (i = 0; i < length; i++) { - tmp = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if ((tmp != STAT_TXADDR_ACK) && (tmp != STAT_TXDATA_ACK)) { - dev_err(i2c->dev, - "%s: bad status before write (0x%x)\n", - __func__, tmp); - return -EIO; - } + result = octeon_i2c_check_status(i2c, false); + if (result) + return result; - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_DATA, data[i]); - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); + octeon_i2c_data_write(i2c, data[i]); + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); result = octeon_i2c_wait(i2c); if (result) @@ -330,219 +904,250 @@ static int octeon_i2c_write(struct octeon_i2c *i2c, int target, } /** - * octeon_i2c_read - receive data from the bus. - * @i2c: The struct octeon_i2c. - * @target: Target address. - * @data: Pointer to the location to store the datae . - * @length: Length of the data. + * octeon_i2c_read - receive data from the bus via low-level controller + * @i2c: The struct octeon_i2c + * @target: Target address + * @data: Pointer to the location to store the data + * @rlength: Length of the data + * @recv_len: flag for length byte * * The address is sent over the bus, then the data is read. * * Returns 0 on success, otherwise a negative errno. */ static int octeon_i2c_read(struct octeon_i2c *i2c, int target, - u8 *data, int length) + u8 *data, u16 *rlength, bool recv_len) { - int i, result; - u8 tmp; + int i, result, length = *rlength; + bool final_read = false; - if (length < 1) - return -EINVAL; + octeon_i2c_data_write(i2c, (target << 1) | 1); + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); - result = octeon_i2c_start(i2c); + result = octeon_i2c_wait(i2c); if (result) return result; - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_DATA, (target<<1) | 1); - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); - - result = octeon_i2c_wait(i2c); + /* address OK ? */ + result = octeon_i2c_check_status(i2c, false); if (result) return result; for (i = 0; i < length; i++) { - tmp = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if ((tmp != STAT_RXDATA_ACK) && (tmp != STAT_RXADDR_ACK)) { - dev_err(i2c->dev, - "%s: bad status before read (0x%x)\n", - __func__, tmp); - return -EIO; - } + /* + * For the last byte to receive TWSI_CTL_AAK must not be set. + * + * A special case is I2C_M_RECV_LEN where we don't know the + * additional length yet. If recv_len is set we assume we're + * not reading the final byte and therefore need to set + * TWSI_CTL_AAK. + */ + if ((i + 1 == length) && !(recv_len && i == 0)) + final_read = true; - if (i+1 < length) - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, - TWSI_CTL_ENAB | TWSI_CTL_AAK); + /* clear iflg to allow next event */ + if (final_read) + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB); else - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, - TWSI_CTL_ENAB); + octeon_i2c_ctl_write(i2c, TWSI_CTL_ENAB | TWSI_CTL_AAK); result = octeon_i2c_wait(i2c); if (result) return result; - data[i] = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_DATA); + data[i] = octeon_i2c_data_read(i2c); + if (recv_len && i == 0) { + if (data[i] > I2C_SMBUS_BLOCK_MAX + 1) + return -EPROTO; + length += data[i]; + } + + result = octeon_i2c_check_status(i2c, final_read); + if (result) + return result; } + *rlength = length; return 0; } /** - * octeon_i2c_xfer - The driver's master_xfer function. - * @adap: Pointer to the i2c_adapter structure. - * @msgs: Pointer to the messages to be processed. - * @num: Length of the MSGS array. + * octeon_i2c_xfer - The driver's master_xfer function + * @adap: Pointer to the i2c_adapter structure + * @msgs: Pointer to the messages to be processed + * @num: Length of the MSGS array * - * Returns the number of messages processed, or a negative errno on - * failure. + * Returns the number of messages processed, or a negative errno on failure. */ -static int octeon_i2c_xfer(struct i2c_adapter *adap, - struct i2c_msg *msgs, +static int octeon_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { - struct i2c_msg *pmsg; - int i; - int ret = 0; struct octeon_i2c *i2c = i2c_get_adapdata(adap); + int i, ret = 0; + + if (num == 1) { + if (msgs[0].len > 0 && msgs[0].len <= 8) { + if (msgs[0].flags & I2C_M_RD) + ret = octeon_i2c_hlc_read(i2c, msgs); + else + ret = octeon_i2c_hlc_write(i2c, msgs); + goto out; + } + } else if (num == 2) { + if ((msgs[0].flags & I2C_M_RD) == 0 && + (msgs[1].flags & I2C_M_RECV_LEN) == 0 && + msgs[0].len > 0 && msgs[0].len <= 2 && + msgs[1].len > 0 && msgs[1].len <= 8 && + msgs[0].addr == msgs[1].addr) { + if (msgs[1].flags & I2C_M_RD) + ret = octeon_i2c_hlc_comp_read(i2c, msgs); + else + ret = octeon_i2c_hlc_comp_write(i2c, msgs); + goto out; + } + } for (i = 0; ret == 0 && i < num; i++) { - pmsg = &msgs[i]; - dev_dbg(i2c->dev, - "Doing %s %d byte(s) to/from 0x%02x - %d of %d messages\n", - pmsg->flags & I2C_M_RD ? "read" : "write", - pmsg->len, pmsg->addr, i + 1, num); + struct i2c_msg *pmsg = &msgs[i]; + + /* zero-length messages are not supported */ + if (!pmsg->len) { + ret = -EOPNOTSUPP; + break; + } + + ret = octeon_i2c_start(i2c); + if (ret) + return ret; + if (pmsg->flags & I2C_M_RD) ret = octeon_i2c_read(i2c, pmsg->addr, pmsg->buf, - pmsg->len); + &pmsg->len, pmsg->flags & I2C_M_RECV_LEN); else ret = octeon_i2c_write(i2c, pmsg->addr, pmsg->buf, - pmsg->len); + pmsg->len); } octeon_i2c_stop(i2c); - +out: return (ret != 0) ? ret : num; } -static u32 octeon_i2c_functionality(struct i2c_adapter *adap) +static int octeon_i2c_get_scl(struct i2c_adapter *adap) { - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + u64 state; + + state = octeon_i2c_read_int(i2c); + return state & TWSI_INT_SCL; } -static const struct i2c_algorithm octeon_i2c_algo = { - .master_xfer = octeon_i2c_xfer, - .functionality = octeon_i2c_functionality, -}; +static void octeon_i2c_set_scl(struct i2c_adapter *adap, int val) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); -static struct i2c_adapter octeon_i2c_ops = { - .owner = THIS_MODULE, - .name = "OCTEON adapter", - .algo = &octeon_i2c_algo, - .timeout = HZ / 50, -}; + octeon_i2c_write_int(i2c, TWSI_INT_SCL_OVR); +} -/** - * octeon_i2c_setclock - Calculate and set clock divisors. - */ -static int octeon_i2c_setclock(struct octeon_i2c *i2c) +static int octeon_i2c_get_sda(struct i2c_adapter *adap) { - int tclk, thp_base, inc, thp_idx, mdiv_idx, ndiv_idx, foscl, diff; - int thp = 0x18, mdiv = 2, ndiv = 0, delta_hz = 1000000; + struct octeon_i2c *i2c = i2c_get_adapdata(adap); + u64 state; - for (ndiv_idx = 0; ndiv_idx < 8 && delta_hz != 0; ndiv_idx++) { - /* - * An mdiv value of less than 2 seems to not work well - * with ds1337 RTCs, so we constrain it to larger - * values. - */ - for (mdiv_idx = 15; mdiv_idx >= 2 && delta_hz != 0; mdiv_idx--) { - /* - * For given ndiv and mdiv values check the - * two closest thp values. - */ - tclk = i2c->twsi_freq * (mdiv_idx + 1) * 10; - tclk *= (1 << ndiv_idx); - thp_base = (i2c->sys_freq / (tclk * 2)) - 1; - for (inc = 0; inc <= 1; inc++) { - thp_idx = thp_base + inc; - if (thp_idx < 5 || thp_idx > 0xff) - continue; + state = octeon_i2c_read_int(i2c); + return state & TWSI_INT_SDA; +} - foscl = i2c->sys_freq / (2 * (thp_idx + 1)); - foscl = foscl / (1 << ndiv_idx); - foscl = foscl / (mdiv_idx + 1) / 10; - diff = abs(foscl - i2c->twsi_freq); - if (diff < delta_hz) { - delta_hz = diff; - thp = thp_idx; - mdiv = mdiv_idx; - ndiv = ndiv_idx; - } - } - } - } - octeon_i2c_write_sw(i2c, SW_TWSI_OP_TWSI_CLK, thp); - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CLKCTL, (mdiv << 3) | ndiv); +static void octeon_i2c_prepare_recovery(struct i2c_adapter *adap) +{ + struct octeon_i2c *i2c = i2c_get_adapdata(adap); - return 0; + /* + * The stop resets the state machine, does not _transmit_ STOP unless + * engine was active. + */ + octeon_i2c_stop(i2c); + + octeon_i2c_hlc_disable(i2c); + octeon_i2c_write_int(i2c, 0); } -static int octeon_i2c_initlowlevel(struct octeon_i2c *i2c) +static void octeon_i2c_unprepare_recovery(struct i2c_adapter *adap) { - u8 status; - int tries; + struct octeon_i2c *i2c = i2c_get_adapdata(adap); - /* disable high level controller, enable bus access */ - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_CTL, TWSI_CTL_ENAB); + octeon_i2c_write_int(i2c, 0); +} - /* reset controller */ - octeon_i2c_write_sw(i2c, SW_TWSI_EOP_TWSI_RST, 0); +static struct i2c_bus_recovery_info octeon_i2c_recovery_info = { + .recover_bus = i2c_generic_scl_recovery, + .get_scl = octeon_i2c_get_scl, + .set_scl = octeon_i2c_set_scl, + .get_sda = octeon_i2c_get_sda, + .prepare_recovery = octeon_i2c_prepare_recovery, + .unprepare_recovery = octeon_i2c_unprepare_recovery, +}; - for (tries = 10; tries; tries--) { - udelay(1); - status = octeon_i2c_read_sw(i2c, SW_TWSI_EOP_TWSI_STAT); - if (status == STAT_IDLE) - return 0; - } - dev_err(i2c->dev, "%s: TWSI_RST failed! (0x%x)\n", __func__, status); - return -EIO; +static u32 octeon_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_SMBUS_BLOCK_PROC_CALL; } +static const struct i2c_algorithm octeon_i2c_algo = { + .master_xfer = octeon_i2c_xfer, + .functionality = octeon_i2c_functionality, +}; + +static struct i2c_adapter octeon_i2c_ops = { + .owner = THIS_MODULE, + .name = "OCTEON adapter", + .algo = &octeon_i2c_algo, +}; + static int octeon_i2c_probe(struct platform_device *pdev) { - int irq, result = 0; - struct octeon_i2c *i2c; + struct device_node *node = pdev->dev.of_node; + int irq, result = 0, hlc_irq = 0; struct resource *res_mem; - - /* All adaptors have an irq. */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; + struct octeon_i2c *i2c; + bool cn78xx_style; + + cn78xx_style = of_device_is_compatible(node, "cavium,octeon-7890-twsi"); + if (cn78xx_style) { + hlc_irq = platform_get_irq(pdev, 0); + if (hlc_irq < 0) + return hlc_irq; + + irq = platform_get_irq(pdev, 2); + if (irq < 0) + return irq; + } else { + /* All adaptors have an irq. */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + } i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); if (!i2c) { - dev_err(&pdev->dev, "kzalloc failed\n"); result = -ENOMEM; goto out; } i2c->dev = &pdev->dev; res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - if (res_mem == NULL) { - dev_err(i2c->dev, "found no memory resource\n"); - result = -ENXIO; + i2c->twsi_base = devm_ioremap_resource(&pdev->dev, res_mem); + if (IS_ERR(i2c->twsi_base)) { + result = PTR_ERR(i2c->twsi_base); goto out; } - i2c->twsi_phys = res_mem->start; - i2c->regsize = resource_size(res_mem); /* * "clock-rate" is a legacy binding, the official binding is * "clock-frequency". Try the official one first and then * fall back if it doesn't exist. */ - if (of_property_read_u32(pdev->dev.of_node, - "clock-frequency", &i2c->twsi_freq) && - of_property_read_u32(pdev->dev.of_node, - "clock-rate", &i2c->twsi_freq)) { + if (of_property_read_u32(node, "clock-frequency", &i2c->twsi_freq) && + of_property_read_u32(node, "clock-rate", &i2c->twsi_freq)) { dev_err(i2c->dev, "no I2C 'clock-rate' or 'clock-frequency' property\n"); result = -ENXIO; @@ -551,17 +1156,35 @@ static int octeon_i2c_probe(struct platform_device *pdev) i2c->sys_freq = octeon_get_io_clock_rate(); - if (!devm_request_mem_region(&pdev->dev, i2c->twsi_phys, i2c->regsize, - res_mem->name)) { - dev_err(i2c->dev, "request_mem_region failed\n"); - goto out; - } - i2c->twsi_base = devm_ioremap(&pdev->dev, i2c->twsi_phys, i2c->regsize); - init_waitqueue_head(&i2c->queue); i2c->irq = irq; + if (cn78xx_style) { + i2c->hlc_irq = hlc_irq; + + i2c->int_enable = octeon_i2c_int_enable78; + i2c->int_disable = octeon_i2c_int_disable78; + i2c->hlc_int_enable = octeon_i2c_hlc_int_enable78; + i2c->hlc_int_disable = octeon_i2c_hlc_int_disable78; + + irq_set_status_flags(i2c->irq, IRQ_NOAUTOEN); + irq_set_status_flags(i2c->hlc_irq, IRQ_NOAUTOEN); + + result = devm_request_irq(&pdev->dev, i2c->hlc_irq, + octeon_i2c_hlc_isr78, 0, + DRV_NAME, i2c); + if (result < 0) { + dev_err(i2c->dev, "failed to attach interrupt\n"); + goto out; + } + } else { + i2c->int_enable = octeon_i2c_int_enable; + i2c->int_disable = octeon_i2c_int_disable; + i2c->hlc_int_enable = octeon_i2c_hlc_int_enable; + i2c->hlc_int_disable = octeon_i2c_int_disable; + } + result = devm_request_irq(&pdev->dev, i2c->irq, octeon_i2c_isr, 0, DRV_NAME, i2c); if (result < 0) { @@ -569,21 +1192,23 @@ static int octeon_i2c_probe(struct platform_device *pdev) goto out; } - result = octeon_i2c_initlowlevel(i2c); + if (OCTEON_IS_MODEL(OCTEON_CN38XX)) + i2c->broken_irq_check = true; + + result = octeon_i2c_init_lowlevel(i2c); if (result) { dev_err(i2c->dev, "init low level failed\n"); goto out; } - result = octeon_i2c_setclock(i2c); - if (result) { - dev_err(i2c->dev, "clock init failed\n"); - goto out; - } + octeon_i2c_set_clock(i2c); i2c->adap = octeon_i2c_ops; + i2c->adap.timeout = msecs_to_jiffies(2); + i2c->adap.retries = 5; + i2c->adap.bus_recovery_info = &octeon_i2c_recovery_info; i2c->adap.dev.parent = &pdev->dev; - i2c->adap.dev.of_node = pdev->dev.of_node; + i2c->adap.dev.of_node = node; i2c_set_adapdata(&i2c->adap, i2c); platform_set_drvdata(pdev, i2c); @@ -592,8 +1217,7 @@ static int octeon_i2c_probe(struct platform_device *pdev) dev_err(i2c->dev, "failed to add adapter\n"); goto out; } - dev_info(i2c->dev, "version %s\n", DRV_VERSION); - + dev_info(i2c->dev, "probed\n"); return 0; out: @@ -608,10 +1232,9 @@ static int octeon_i2c_remove(struct platform_device *pdev) return 0; }; -static struct of_device_id octeon_i2c_match[] = { - { - .compatible = "cavium,octeon-3860-twsi", - }, +static const struct of_device_id octeon_i2c_match[] = { + { .compatible = "cavium,octeon-3860-twsi", }, + { .compatible = "cavium,octeon-7890-twsi", }, {}, }; MODULE_DEVICE_TABLE(of, octeon_i2c_match); @@ -630,4 +1253,3 @@ module_platform_driver(octeon_i2c_driver); MODULE_AUTHOR("Michael Lawnick "); MODULE_DESCRIPTION("I2C-Bus adapter for Cavium OCTEON processors"); MODULE_LICENSE("GPL"); -MODULE_VERSION(DRV_VERSION); diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index 08d26ba..0c88645 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -185,7 +185,6 @@ enum { #define OMAP_I2C_IP_V2_INTERRUPTS_MASK 0x6FFF struct omap_i2c_dev { - spinlock_t lock; /* IRQ synchronization */ struct device *dev; void __iomem *base; /* virtual */ int irq; @@ -1011,12 +1010,10 @@ static irqreturn_t omap_i2c_isr_thread(int this_irq, void *dev_id) { struct omap_i2c_dev *omap = dev_id; - unsigned long flags; u16 bits; u16 stat; int err = 0, count = 0; - spin_lock_irqsave(&omap->lock, flags); do { bits = omap_i2c_read_reg(omap, OMAP_I2C_IE_REG); stat = omap_i2c_read_reg(omap, OMAP_I2C_STAT_REG); @@ -1142,8 +1139,6 @@ omap_i2c_isr_thread(int this_irq, void *dev_id) omap_i2c_complete_cmd(omap, err); out: - spin_unlock_irqrestore(&omap->lock, flags); - return IRQ_HANDLED; } @@ -1330,8 +1325,6 @@ omap_i2c_probe(struct platform_device *pdev) omap->dev = &pdev->dev; omap->irq = irq; - spin_lock_init(&omap->lock); - platform_set_drvdata(pdev, omap); init_completion(&omap->cmd_complete); @@ -1450,7 +1443,8 @@ omap_i2c_probe(struct platform_device *pdev) err_unuse_clocks: omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, 0); - pm_runtime_put(omap->dev); + pm_runtime_dont_use_autosuspend(omap->dev); + pm_runtime_put_sync(omap->dev); pm_runtime_disable(&pdev->dev); err_free_mem: @@ -1468,6 +1462,7 @@ static int omap_i2c_remove(struct platform_device *pdev) return ret; omap_i2c_write_reg(omap, OMAP_I2C_CON_REG, 0); + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return 0; diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c index 630bce6..23d1c16 100644 --- a/drivers/i2c/busses/i2c-piix4.c +++ b/drivers/i2c/busses/i2c-piix4.c @@ -23,6 +23,9 @@ Note: we assume there can only be one device, with one or more SMBus interfaces. + The device can register multiple i2c_adapters (up to PIIX4_MAX_ADAPTERS). + For devices supporting multiple ports the i2c_adapter should provide + an i2c_algorithm to access them. */ #include @@ -37,6 +40,7 @@ #include #include #include +#include /* PIIX4 SMBus address offsets */ @@ -75,6 +79,22 @@ #define PIIX4_WORD_DATA 0x0C #define PIIX4_BLOCK_DATA 0x14 +/* Multi-port constants */ +#define PIIX4_MAX_ADAPTERS 4 + +/* SB800 constants */ +#define SB800_PIIX4_SMB_IDX 0xcd6 + +/* + * SB800 port is selected by bits 2:1 of the smb_en register (0x2c) + * or the smb_sel register (0x2e), depending on bit 0 of register 0x2f. + * Hudson-2/Bolton port is always selected by bits 2:1 of register 0x2f. + */ +#define SB800_PIIX4_PORT_IDX 0x2c +#define SB800_PIIX4_PORT_IDX_ALT 0x2e +#define SB800_PIIX4_PORT_IDX_SEL 0x2f +#define SB800_PIIX4_PORT_IDX_MASK 0x06 + /* insmod parameters */ /* If force is set to anything different from 0, we forcibly enable the @@ -122,8 +142,24 @@ static const struct dmi_system_id piix4_dmi_ibm[] = { { }, }; +/* + * SB800 globals + * piix4_mutex_sb800 protects piix4_port_sel_sb800 and the pair + * of I/O ports at SB800_PIIX4_SMB_IDX. + */ +static DEFINE_MUTEX(piix4_mutex_sb800); +static u8 piix4_port_sel_sb800; +static const char *piix4_main_port_names_sb800[PIIX4_MAX_ADAPTERS] = { + " port 0", " port 2", " port 3", " port 4" +}; +static const char *piix4_aux_port_name_sb800 = " port 1"; + struct i2c_piix4_adapdata { unsigned short smba; + + /* SB800 */ + bool sb800_main; + u8 port; /* Port number, shifted */ }; static int piix4_setup(struct pci_dev *PIIX4_dev, @@ -229,8 +265,7 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev, const struct pci_device_id *id, u8 aux) { unsigned short piix4_smba; - unsigned short smba_idx = 0xcd6; - u8 smba_en_lo, smba_en_hi, smb_en, smb_en_status; + u8 smba_en_lo, smba_en_hi, smb_en, smb_en_status, port_sel; u8 i2ccfg, i2ccfg_offset = 0x10; /* SB800 and later SMBus does not support forcing address */ @@ -251,16 +286,12 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev, else smb_en = (aux) ? 0x28 : 0x2c; - if (!request_region(smba_idx, 2, "smba_idx")) { - dev_err(&PIIX4_dev->dev, "SMBus base address index region " - "0x%x already in use!\n", smba_idx); - return -EBUSY; - } - outb_p(smb_en, smba_idx); - smba_en_lo = inb_p(smba_idx + 1); - outb_p(smb_en + 1, smba_idx); - smba_en_hi = inb_p(smba_idx + 1); - release_region(smba_idx, 2); + mutex_lock(&piix4_mutex_sb800); + outb_p(smb_en, SB800_PIIX4_SMB_IDX); + smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1); + outb_p(smb_en + 1, SB800_PIIX4_SMB_IDX); + smba_en_hi = inb_p(SB800_PIIX4_SMB_IDX + 1); + mutex_unlock(&piix4_mutex_sb800); if (!smb_en) { smb_en_status = smba_en_lo & 0x10; @@ -314,6 +345,23 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev, "SMBus Host Controller at 0x%x, revision %d\n", piix4_smba, i2ccfg >> 4); + /* Find which register is used for port selection */ + if (PIIX4_dev->vendor == PCI_VENDOR_ID_AMD) { + piix4_port_sel_sb800 = SB800_PIIX4_PORT_IDX_ALT; + } else { + mutex_lock(&piix4_mutex_sb800); + outb_p(SB800_PIIX4_PORT_IDX_SEL, SB800_PIIX4_SMB_IDX); + port_sel = inb_p(SB800_PIIX4_SMB_IDX + 1); + piix4_port_sel_sb800 = (port_sel & 0x01) ? + SB800_PIIX4_PORT_IDX_ALT : + SB800_PIIX4_PORT_IDX; + mutex_unlock(&piix4_mutex_sb800); + } + + dev_info(&PIIX4_dev->dev, + "Using register 0x%02x for SMBus port selection\n", + (unsigned int)piix4_port_sel_sb800); + return piix4_smba; } @@ -483,7 +531,7 @@ static s32 piix4_access(struct i2c_adapter * adap, u16 addr, if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) return -EINVAL; outb_p(len, SMBHSTDAT0); - i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ for (i = 1; i <= len; i++) outb_p(data->block[i], SMBBLKDAT); } @@ -516,7 +564,7 @@ static s32 piix4_access(struct i2c_adapter * adap, u16 addr, data->block[0] = inb_p(SMBHSTDAT0); if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) return -EPROTO; - i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ + inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ for (i = 1; i <= data->block[0]; i++) data->block[i] = inb_p(SMBBLKDAT); break; @@ -524,6 +572,43 @@ static s32 piix4_access(struct i2c_adapter * adap, u16 addr, return 0; } +/* + * Handles access to multiple SMBus ports on the SB800. + * The port is selected by bits 2:1 of the smb_en register (0x2c). + * Returns negative errno on error. + * + * Note: The selected port must be returned to the initial selection to avoid + * problems on certain systems. + */ +static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data *data) +{ + struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap); + u8 smba_en_lo; + u8 port; + int retval; + + mutex_lock(&piix4_mutex_sb800); + + outb_p(piix4_port_sel_sb800, SB800_PIIX4_SMB_IDX); + smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1); + + port = adapdata->port; + if ((smba_en_lo & SB800_PIIX4_PORT_IDX_MASK) != port) + outb_p((smba_en_lo & ~SB800_PIIX4_PORT_IDX_MASK) | port, + SB800_PIIX4_SMB_IDX + 1); + + retval = piix4_access(adap, addr, flags, read_write, + command, size, data); + + outb_p(smba_en_lo, SB800_PIIX4_SMB_IDX + 1); + + mutex_unlock(&piix4_mutex_sb800); + + return retval; +} + static u32 piix4_func(struct i2c_adapter *adapter) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | @@ -536,6 +621,11 @@ static const struct i2c_algorithm smbus_algorithm = { .functionality = piix4_func, }; +static const struct i2c_algorithm piix4_smbus_algorithm_sb800 = { + .smbus_xfer = piix4_access_sb800, + .functionality = piix4_func, +}; + static const struct pci_device_id piix4_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3) }, @@ -561,11 +651,12 @@ static const struct pci_device_id piix4_ids[] = { MODULE_DEVICE_TABLE (pci, piix4_ids); -static struct i2c_adapter *piix4_main_adapter; +static struct i2c_adapter *piix4_main_adapters[PIIX4_MAX_ADAPTERS]; static struct i2c_adapter *piix4_aux_adapter; static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, - struct i2c_adapter **padap) + bool sb800_main, u8 port, + const char *name, struct i2c_adapter **padap) { struct i2c_adapter *adap; struct i2c_piix4_adapdata *adapdata; @@ -579,7 +670,8 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; - adap->algo = &smbus_algorithm; + adap->algo = sb800_main ? &piix4_smbus_algorithm_sb800 + : &smbus_algorithm; adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL); if (adapdata == NULL) { @@ -589,12 +681,14 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, } adapdata->smba = smba; + adapdata->sb800_main = sb800_main; + adapdata->port = port << 1; /* set up the sysfs linkage to our parent device */ adap->dev.parent = &dev->dev; snprintf(adap->name, sizeof(adap->name), - "SMBus PIIX4 adapter at %04x", smba); + "SMBus PIIX4 adapter%s at %04x", name, smba); i2c_set_adapdata(adap, adapdata); @@ -611,27 +705,83 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, return 0; } +static int piix4_add_adapters_sb800(struct pci_dev *dev, unsigned short smba) +{ + struct i2c_piix4_adapdata *adapdata; + int port; + int retval; + + for (port = 0; port < PIIX4_MAX_ADAPTERS; port++) { + retval = piix4_add_adapter(dev, smba, true, port, + piix4_main_port_names_sb800[port], + &piix4_main_adapters[port]); + if (retval < 0) + goto error; + } + + return retval; + +error: + dev_err(&dev->dev, + "Error setting up SB800 adapters. Unregistering!\n"); + while (--port >= 0) { + adapdata = i2c_get_adapdata(piix4_main_adapters[port]); + if (adapdata->smba) { + i2c_del_adapter(piix4_main_adapters[port]); + kfree(adapdata); + kfree(piix4_main_adapters[port]); + piix4_main_adapters[port] = NULL; + } + } + + return retval; +} + static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id) { int retval; + bool is_sb800 = false; if ((dev->vendor == PCI_VENDOR_ID_ATI && dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && dev->revision >= 0x40) || - dev->vendor == PCI_VENDOR_ID_AMD) + dev->vendor == PCI_VENDOR_ID_AMD) { + is_sb800 = true; + + if (!request_region(SB800_PIIX4_SMB_IDX, 2, "smba_idx")) { + dev_err(&dev->dev, + "SMBus base address index region 0x%x already in use!\n", + SB800_PIIX4_SMB_IDX); + return -EBUSY; + } + /* base address location etc changed in SB800 */ retval = piix4_setup_sb800(dev, id, 0); - else - retval = piix4_setup(dev, id); + if (retval < 0) { + release_region(SB800_PIIX4_SMB_IDX, 2); + return retval; + } - /* If no main SMBus found, give up */ - if (retval < 0) - return retval; + /* + * Try to register multiplexed main SMBus adapter, + * give up if we can't + */ + retval = piix4_add_adapters_sb800(dev, retval); + if (retval < 0) { + release_region(SB800_PIIX4_SMB_IDX, 2); + return retval; + } + } else { + retval = piix4_setup(dev, id); + if (retval < 0) + return retval; - /* Try to register main SMBus adapter, give up if we can't */ - retval = piix4_add_adapter(dev, retval, &piix4_main_adapter); - if (retval < 0) - return retval; + /* Try to register main SMBus adapter, give up if we can't */ + retval = piix4_add_adapter(dev, retval, false, 0, "", + &piix4_main_adapters[0]); + if (retval < 0) + return retval; + } /* Check for auxiliary SMBus on some AMD chipsets */ retval = -ENODEV; @@ -654,7 +804,9 @@ static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id) if (retval > 0) { /* Try to add the aux adapter if it exists, * piix4_add_adapter will clean up if this fails */ - piix4_add_adapter(dev, retval, &piix4_aux_adapter); + piix4_add_adapter(dev, retval, false, 0, + is_sb800 ? piix4_aux_port_name_sb800 : "", + &piix4_aux_adapter); } return 0; @@ -666,7 +818,11 @@ static void piix4_adap_remove(struct i2c_adapter *adap) if (adapdata->smba) { i2c_del_adapter(adap); - release_region(adapdata->smba, SMBIOSIZE); + if (adapdata->port == (0 << 1)) { + release_region(adapdata->smba, SMBIOSIZE); + if (adapdata->sb800_main) + release_region(SB800_PIIX4_SMB_IDX, 2); + } kfree(adapdata); kfree(adap); } @@ -674,9 +830,13 @@ static void piix4_adap_remove(struct i2c_adapter *adap) static void piix4_remove(struct pci_dev *dev) { - if (piix4_main_adapter) { - piix4_adap_remove(piix4_main_adapter); - piix4_main_adapter = NULL; + int port = PIIX4_MAX_ADAPTERS; + + while (--port >= 0) { + if (piix4_main_adapters[port]) { + piix4_adap_remove(piix4_main_adapters[port]); + piix4_main_adapters[port] = NULL; + } } if (piix4_aux_adapter) { diff --git a/drivers/i2c/busses/i2c-powermac.c b/drivers/i2c/busses/i2c-powermac.c index 6abcf69..b0d9dee 100644 --- a/drivers/i2c/busses/i2c-powermac.c +++ b/drivers/i2c/busses/i2c-powermac.c @@ -150,13 +150,11 @@ static int i2c_powermac_master_xfer( struct i2c_adapter *adap, { struct pmac_i2c_bus *bus = i2c_get_adapdata(adap); int rc = 0; - int read; int addrdir; if (msgs->flags & I2C_M_TEN) return -EINVAL; - read = (msgs->flags & I2C_M_RD) != 0; - addrdir = (msgs->addr << 1) | read; + addrdir = i2c_8bit_addr_from_msg(msgs); rc = pmac_i2c_open(bus, 0); if (rc) { diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c index fdcbdab..041050e 100644 --- a/drivers/i2c/busses/i2c-qup.c +++ b/drivers/i2c/busses/i2c-qup.c @@ -14,8 +14,12 @@ * */ +#include #include #include +#include +#include +#include #include #include #include @@ -24,6 +28,7 @@ #include #include #include +#include /* QUP Registers */ #define QUP_CONFIG 0x000 @@ -33,6 +38,7 @@ #define QUP_OPERATIONAL 0x018 #define QUP_ERROR_FLAGS 0x01c #define QUP_ERROR_FLAGS_EN 0x020 +#define QUP_OPERATIONAL_MASK 0x028 #define QUP_HW_VERSION 0x030 #define QUP_MX_OUTPUT_CNT 0x100 #define QUP_OUT_FIFO_BASE 0x110 @@ -42,6 +48,7 @@ #define QUP_IN_FIFO_BASE 0x218 #define QUP_I2C_CLK_CTL 0x400 #define QUP_I2C_STATUS 0x404 +#define QUP_I2C_MASTER_GEN 0x408 /* QUP States and reset values */ #define QUP_RESET_STATE 0 @@ -51,6 +58,7 @@ #define QUP_STATE_VALID BIT(2) #define QUP_I2C_MAST_GEN BIT(4) +#define QUP_I2C_FLUSH BIT(6) #define QUP_OPERATIONAL_RESET 0x000ff0 #define QUP_I2C_STATUS_RESET 0xfffffc @@ -69,16 +77,22 @@ #define QUP_CLOCK_AUTO_GATE BIT(13) #define I2C_MINI_CORE (2 << 8) #define I2C_N_VAL 15 +#define I2C_N_VAL_V2 7 + /* Most significant word offset in FIFO port */ #define QUP_MSW_SHIFT (I2C_N_VAL + 1) /* Packing/Unpacking words in FIFOs, and IO modes */ #define QUP_OUTPUT_BLK_MODE (1 << 10) +#define QUP_OUTPUT_BAM_MODE (3 << 10) #define QUP_INPUT_BLK_MODE (1 << 12) +#define QUP_INPUT_BAM_MODE (3 << 12) +#define QUP_BAM_MODE (QUP_OUTPUT_BAM_MODE | QUP_INPUT_BAM_MODE) #define QUP_UNPACK_EN BIT(14) #define QUP_PACK_EN BIT(15) #define QUP_REPACK_EN (QUP_UNPACK_EN | QUP_PACK_EN) +#define QUP_V2_TAGS_EN 1 #define QUP_OUTPUT_BLOCK_SIZE(x)(((x) >> 0) & 0x03) #define QUP_OUTPUT_FIFO_SIZE(x) (((x) >> 2) & 0x07) @@ -90,6 +104,15 @@ #define QUP_TAG_DATA (2 << 8) #define QUP_TAG_STOP (3 << 8) #define QUP_TAG_REC (4 << 8) +#define QUP_BAM_INPUT_EOT 0x93 +#define QUP_BAM_FLUSH_STOP 0x96 + +/* QUP v2 tags */ +#define QUP_TAG_V2_START 0x81 +#define QUP_TAG_V2_DATAWR 0x82 +#define QUP_TAG_V2_DATAWR_STOP 0x83 +#define QUP_TAG_V2_DATARD 0x85 +#define QUP_TAG_V2_DATARD_STOP 0x87 /* Status, Error flags */ #define I2C_STATUS_WR_BUFFER_FULL BIT(0) @@ -98,6 +121,36 @@ #define QUP_STATUS_ERROR_FLAGS 0x7c #define QUP_READ_LIMIT 256 +#define SET_BIT 0x1 +#define RESET_BIT 0x0 +#define ONE_BYTE 0x1 +#define QUP_I2C_MX_CONFIG_DURING_RUN BIT(31) + +#define MX_TX_RX_LEN SZ_64K +#define MX_BLOCKS (MX_TX_RX_LEN / QUP_READ_LIMIT) + +/* Max timeout in ms for 32k bytes */ +#define TOUT_MAX 300 + +struct qup_i2c_block { + int count; + int pos; + int tx_tag_len; + int rx_tag_len; + int data_len; + u8 tags[6]; +}; + +struct qup_i2c_tag { + u8 *start; + dma_addr_t addr; +}; + +struct qup_i2c_bam { + struct qup_i2c_tag tag; + struct dma_chan *dma; + struct scatterlist *sg; +}; struct qup_i2c_dev { struct device *dev; @@ -114,6 +167,7 @@ struct qup_i2c_dev { int in_blk_sz; unsigned long one_byte_t; + struct qup_i2c_block blk; struct i2c_msg *msg; /* Current posion in user message buffer */ @@ -123,6 +177,19 @@ struct qup_i2c_dev { /* QUP core errors */ u32 qup_err; + /* To check if this is the last msg */ + bool is_last; + + /* To configure when bus is in run state */ + int config_run; + + /* dma parameters */ + bool is_dma; + struct dma_pool *dpool; + struct qup_i2c_tag start_tag; + struct qup_i2c_bam brx; + struct qup_i2c_bam btx; + struct completion xfer; }; @@ -199,6 +266,14 @@ static int qup_i2c_poll_state(struct qup_i2c_dev *qup, u32 req_state) return qup_i2c_poll_state_mask(qup, req_state, QUP_STATE_MASK); } +static void qup_i2c_flush(struct qup_i2c_dev *qup) +{ + u32 val = readl(qup->base + QUP_STATE); + + val |= QUP_I2C_FLUSH; + writel(val, qup->base + QUP_STATE); +} + static int qup_i2c_poll_state_valid(struct qup_i2c_dev *qup) { return qup_i2c_poll_state_mask(qup, 0, 0); @@ -221,26 +296,62 @@ static int qup_i2c_change_state(struct qup_i2c_dev *qup, u32 state) return 0; } -static int qup_i2c_wait_writeready(struct qup_i2c_dev *qup) +/** + * qup_i2c_wait_ready - wait for a give number of bytes in tx/rx path + * @qup: The qup_i2c_dev device + * @op: The bit/event to wait on + * @val: value of the bit to wait on, 0 or 1 + * @len: The length the bytes to be transferred + */ +static int qup_i2c_wait_ready(struct qup_i2c_dev *qup, int op, bool val, + int len) { unsigned long timeout; u32 opflags; u32 status; + u32 shift = __ffs(op); - timeout = jiffies + HZ; + len *= qup->one_byte_t; + /* timeout after a wait of twice the max time */ + timeout = jiffies + len * 4; for (;;) { opflags = readl(qup->base + QUP_OPERATIONAL); status = readl(qup->base + QUP_I2C_STATUS); - if (!(opflags & QUP_OUT_NOT_EMPTY) && - !(status & I2C_STATUS_BUS_ACTIVE)) - return 0; + if (((opflags & op) >> shift) == val) { + if ((op == QUP_OUT_NOT_EMPTY) && qup->is_last) { + if (!(status & I2C_STATUS_BUS_ACTIVE)) + return 0; + } else { + return 0; + } + } if (time_after(jiffies, timeout)) return -ETIMEDOUT; - usleep_range(qup->one_byte_t, qup->one_byte_t * 2); + usleep_range(len, len * 2); + } +} + +static void qup_i2c_set_write_mode_v2(struct qup_i2c_dev *qup, + struct i2c_msg *msg) +{ + /* Number of entries to shift out, including the tags */ + int total = msg->len + qup->blk.tx_tag_len; + + total |= qup->config_run; + + if (total < qup->out_fifo_sz) { + /* FIFO mode */ + writel(QUP_REPACK_EN, qup->base + QUP_IO_MODE); + writel(total, qup->base + QUP_MX_WRITE_CNT); + } else { + /* BLOCK mode (transfer data on chunks) */ + writel(QUP_OUTPUT_BLK_MODE | QUP_REPACK_EN, + qup->base + QUP_IO_MODE); + writel(total, qup->base + QUP_MX_OUTPUT_CNT); } } @@ -261,13 +372,45 @@ static void qup_i2c_set_write_mode(struct qup_i2c_dev *qup, struct i2c_msg *msg) } } -static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static int check_for_fifo_space(struct qup_i2c_dev *qup) +{ + int ret; + + ret = qup_i2c_change_state(qup, QUP_PAUSE_STATE); + if (ret) + goto out; + + ret = qup_i2c_wait_ready(qup, QUP_OUT_FULL, + RESET_BIT, 4 * ONE_BYTE); + if (ret) { + /* Fifo is full. Drain out the fifo */ + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto out; + + ret = qup_i2c_wait_ready(qup, QUP_OUT_NOT_EMPTY, + RESET_BIT, 256 * ONE_BYTE); + if (ret) { + dev_err(qup->dev, "timeout for fifo out full"); + goto out; + } + + ret = qup_i2c_change_state(qup, QUP_PAUSE_STATE); + if (ret) + goto out; + } + +out: + return ret; +} + +static int qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) { u32 addr = msg->addr << 1; u32 qup_tag; - u32 opflags; int idx; u32 val; + int ret = 0; if (qup->pos == 0) { val = QUP_TAG_START | addr; @@ -279,9 +422,9 @@ static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) while (qup->pos < msg->len) { /* Check that there's space in the FIFO for our pair */ - opflags = readl(qup->base + QUP_OPERATIONAL); - if (opflags & QUP_OUT_FULL) - break; + ret = check_for_fifo_space(qup); + if (ret) + return ret; if (qup->pos == msg->len - 1) qup_tag = QUP_TAG_STOP; @@ -300,11 +443,501 @@ static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) qup->pos++; idx++; } + + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + + return ret; } -static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_set_blk_data(struct qup_i2c_dev *qup, + struct i2c_msg *msg) +{ + memset(&qup->blk, 0, sizeof(qup->blk)); + + qup->blk.data_len = msg->len; + qup->blk.count = (msg->len + QUP_READ_LIMIT - 1) / QUP_READ_LIMIT; + + /* 4 bytes for first block and 2 writes for rest */ + qup->blk.tx_tag_len = 4 + (qup->blk.count - 1) * 2; + + /* There are 2 tag bytes that are read in to fifo for every block */ + if (msg->flags & I2C_M_RD) + qup->blk.rx_tag_len = qup->blk.count * 2; +} + +static int qup_i2c_send_data(struct qup_i2c_dev *qup, int tlen, u8 *tbuf, + int dlen, u8 *dbuf) +{ + u32 val = 0, idx = 0, pos = 0, i = 0, t; + int len = tlen + dlen; + u8 *buf = tbuf; + int ret = 0; + + while (len > 0) { + ret = check_for_fifo_space(qup); + if (ret) + return ret; + + t = (len >= 4) ? 4 : len; + + while (idx < t) { + if (!i && (pos >= tlen)) { + buf = dbuf; + pos = 0; + i = 1; + } + val |= buf[pos++] << (idx++ * 8); + } + + writel(val, qup->base + QUP_OUT_FIFO_BASE); + idx = 0; + val = 0; + len -= 4; + } + + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + + return ret; +} + +static int qup_i2c_get_data_len(struct qup_i2c_dev *qup) +{ + int data_len; + + if (qup->blk.data_len > QUP_READ_LIMIT) + data_len = QUP_READ_LIMIT; + else + data_len = qup->blk.data_len; + + return data_len; +} + +static int qup_i2c_set_tags(u8 *tags, struct qup_i2c_dev *qup, + struct i2c_msg *msg, int is_dma) +{ + u16 addr = i2c_8bit_addr_from_msg(msg); + int len = 0; + int data_len; + + int last = (qup->blk.pos == (qup->blk.count - 1)) && (qup->is_last); + + if (qup->blk.pos == 0) { + tags[len++] = QUP_TAG_V2_START; + tags[len++] = addr & 0xff; + + if (msg->flags & I2C_M_TEN) + tags[len++] = addr >> 8; + } + + /* Send _STOP commands for the last block */ + if (last) { + if (msg->flags & I2C_M_RD) + tags[len++] = QUP_TAG_V2_DATARD_STOP; + else + tags[len++] = QUP_TAG_V2_DATAWR_STOP; + } else { + if (msg->flags & I2C_M_RD) + tags[len++] = QUP_TAG_V2_DATARD; + else + tags[len++] = QUP_TAG_V2_DATAWR; + } + + data_len = qup_i2c_get_data_len(qup); + + /* 0 implies 256 bytes */ + if (data_len == QUP_READ_LIMIT) + tags[len++] = 0; + else + tags[len++] = data_len; + + if ((msg->flags & I2C_M_RD) && last && is_dma) { + tags[len++] = QUP_BAM_INPUT_EOT; + tags[len++] = QUP_BAM_FLUSH_STOP; + } + + return len; +} + +static int qup_i2c_issue_xfer_v2(struct qup_i2c_dev *qup, struct i2c_msg *msg) +{ + int data_len = 0, tag_len, index; + int ret; + + tag_len = qup_i2c_set_tags(qup->blk.tags, qup, msg, 0); + index = msg->len - qup->blk.data_len; + + /* only tags are written for read */ + if (!(msg->flags & I2C_M_RD)) + data_len = qup_i2c_get_data_len(qup); + + ret = qup_i2c_send_data(qup, tag_len, qup->blk.tags, + data_len, &msg->buf[index]); + qup->blk.data_len -= data_len; + + return ret; +} + +static void qup_i2c_bam_cb(void *data) +{ + struct qup_i2c_dev *qup = data; + + complete(&qup->xfer); +} + +static int qup_sg_set_buf(struct scatterlist *sg, void *buf, + struct qup_i2c_tag *tg, unsigned int buflen, + struct qup_i2c_dev *qup, int map, int dir) +{ + int ret; + + sg_set_buf(sg, buf, buflen); + ret = dma_map_sg(qup->dev, sg, 1, dir); + if (!ret) + return -EINVAL; + + if (!map) + sg_dma_address(sg) = tg->addr + ((u8 *)buf - tg->start); + + return 0; +} + +static void qup_i2c_rel_dma(struct qup_i2c_dev *qup) +{ + if (qup->btx.dma) + dma_release_channel(qup->btx.dma); + if (qup->brx.dma) + dma_release_channel(qup->brx.dma); + qup->btx.dma = NULL; + qup->brx.dma = NULL; +} + +static int qup_i2c_req_dma(struct qup_i2c_dev *qup) +{ + int err; + + if (!qup->btx.dma) { + qup->btx.dma = dma_request_slave_channel_reason(qup->dev, "tx"); + if (IS_ERR(qup->btx.dma)) { + err = PTR_ERR(qup->btx.dma); + qup->btx.dma = NULL; + dev_err(qup->dev, "\n tx channel not available"); + return err; + } + } + + if (!qup->brx.dma) { + qup->brx.dma = dma_request_slave_channel_reason(qup->dev, "rx"); + if (IS_ERR(qup->brx.dma)) { + dev_err(qup->dev, "\n rx channel not available"); + err = PTR_ERR(qup->brx.dma); + qup->brx.dma = NULL; + qup_i2c_rel_dma(qup); + return err; + } + } + return 0; +} + +static int qup_i2c_bam_do_xfer(struct qup_i2c_dev *qup, struct i2c_msg *msg, + int num) +{ + struct dma_async_tx_descriptor *txd, *rxd = NULL; + int ret = 0, idx = 0, limit = QUP_READ_LIMIT; + dma_cookie_t cookie_rx, cookie_tx; + u32 rx_nents = 0, tx_nents = 0, len, blocks, rem; + u32 i, tlen, tx_len, tx_buf = 0, rx_buf = 0, off = 0; + u8 *tags; + + while (idx < num) { + blocks = (msg->len + limit) / limit; + rem = msg->len % limit; + tx_len = 0, len = 0, i = 0; + + qup->is_last = (idx == (num - 1)); + + qup_i2c_set_blk_data(qup, msg); + + if (msg->flags & I2C_M_RD) { + rx_nents += (blocks * 2) + 1; + tx_nents += 1; + + while (qup->blk.pos < blocks) { + /* length set to '0' implies 256 bytes */ + tlen = (i == (blocks - 1)) ? rem : 0; + tags = &qup->start_tag.start[off + len]; + len += qup_i2c_set_tags(tags, qup, msg, 1); + + /* scratch buf to read the start and len tags */ + ret = qup_sg_set_buf(&qup->brx.sg[rx_buf++], + &qup->brx.tag.start[0], + &qup->brx.tag, + 2, qup, 0, 0); + + if (ret) + return ret; + + ret = qup_sg_set_buf(&qup->brx.sg[rx_buf++], + &msg->buf[limit * i], + NULL, tlen, qup, + 1, DMA_FROM_DEVICE); + if (ret) + return ret; + + i++; + qup->blk.pos = i; + } + ret = qup_sg_set_buf(&qup->btx.sg[tx_buf++], + &qup->start_tag.start[off], + &qup->start_tag, len, qup, 0, 0); + if (ret) + return ret; + + off += len; + /* scratch buf to read the BAM EOT and FLUSH tags */ + ret = qup_sg_set_buf(&qup->brx.sg[rx_buf++], + &qup->brx.tag.start[0], + &qup->brx.tag, 2, + qup, 0, 0); + if (ret) + return ret; + } else { + tx_nents += (blocks * 2); + + while (qup->blk.pos < blocks) { + tlen = (i == (blocks - 1)) ? rem : 0; + tags = &qup->start_tag.start[off + tx_len]; + len = qup_i2c_set_tags(tags, qup, msg, 1); + + ret = qup_sg_set_buf(&qup->btx.sg[tx_buf++], + tags, + &qup->start_tag, len, + qup, 0, 0); + if (ret) + return ret; + + tx_len += len; + ret = qup_sg_set_buf(&qup->btx.sg[tx_buf++], + &msg->buf[limit * i], + NULL, tlen, qup, 1, + DMA_TO_DEVICE); + if (ret) + return ret; + i++; + qup->blk.pos = i; + } + off += tx_len; + + if (idx == (num - 1)) { + len = 1; + if (rx_nents) { + qup->btx.tag.start[0] = + QUP_BAM_INPUT_EOT; + len++; + } + qup->btx.tag.start[len - 1] = + QUP_BAM_FLUSH_STOP; + ret = qup_sg_set_buf(&qup->btx.sg[tx_buf++], + &qup->btx.tag.start[0], + &qup->btx.tag, len, + qup, 0, 0); + if (ret) + return ret; + tx_nents += 1; + } + } + idx++; + msg++; + } + + txd = dmaengine_prep_slave_sg(qup->btx.dma, qup->btx.sg, tx_nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_PREP_FENCE); + if (!txd) { + dev_err(qup->dev, "failed to get tx desc\n"); + ret = -EINVAL; + goto desc_err; + } + + if (!rx_nents) { + txd->callback = qup_i2c_bam_cb; + txd->callback_param = qup; + } + + cookie_tx = dmaengine_submit(txd); + if (dma_submit_error(cookie_tx)) { + ret = -EINVAL; + goto desc_err; + } + + dma_async_issue_pending(qup->btx.dma); + + if (rx_nents) { + rxd = dmaengine_prep_slave_sg(qup->brx.dma, qup->brx.sg, + rx_nents, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!rxd) { + dev_err(qup->dev, "failed to get rx desc\n"); + ret = -EINVAL; + + /* abort TX descriptors */ + dmaengine_terminate_all(qup->btx.dma); + goto desc_err; + } + + rxd->callback = qup_i2c_bam_cb; + rxd->callback_param = qup; + cookie_rx = dmaengine_submit(rxd); + if (dma_submit_error(cookie_rx)) { + ret = -EINVAL; + goto desc_err; + } + + dma_async_issue_pending(qup->brx.dma); + } + + if (!wait_for_completion_timeout(&qup->xfer, TOUT_MAX * HZ)) { + dev_err(qup->dev, "normal trans timed out\n"); + ret = -ETIMEDOUT; + } + + if (ret || qup->bus_err || qup->qup_err) { + if (qup->bus_err & QUP_I2C_NACK_FLAG) { + msg--; + dev_err(qup->dev, "NACK from %x\n", msg->addr); + ret = -EIO; + + if (qup_i2c_change_state(qup, QUP_RUN_STATE)) { + dev_err(qup->dev, "change to run state timed out"); + return ret; + } + + if (rx_nents) + writel(QUP_BAM_INPUT_EOT, + qup->base + QUP_OUT_FIFO_BASE); + + writel(QUP_BAM_FLUSH_STOP, + qup->base + QUP_OUT_FIFO_BASE); + + qup_i2c_flush(qup); + + /* wait for remaining interrupts to occur */ + if (!wait_for_completion_timeout(&qup->xfer, HZ)) + dev_err(qup->dev, "flush timed out\n"); + + qup_i2c_rel_dma(qup); + } + } + + dma_unmap_sg(qup->dev, qup->btx.sg, tx_nents, DMA_TO_DEVICE); + + if (rx_nents) + dma_unmap_sg(qup->dev, qup->brx.sg, rx_nents, + DMA_FROM_DEVICE); +desc_err: + return ret; +} + +static int qup_i2c_bam_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, + int num) +{ + struct qup_i2c_dev *qup = i2c_get_adapdata(adap); + int ret = 0; + + enable_irq(qup->irq); + ret = qup_i2c_req_dma(qup); + + if (ret) + goto out; + + qup->bus_err = 0; + qup->qup_err = 0; + + writel(0, qup->base + QUP_MX_INPUT_CNT); + writel(0, qup->base + QUP_MX_OUTPUT_CNT); + + /* set BAM mode */ + writel(QUP_REPACK_EN | QUP_BAM_MODE, qup->base + QUP_IO_MODE); + + /* mask fifo irqs */ + writel((0x3 << 8), qup->base + QUP_OPERATIONAL_MASK); + + /* set RUN STATE */ + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto out; + + writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); + + qup->msg = msg; + ret = qup_i2c_bam_do_xfer(qup, qup->msg, num); +out: + disable_irq(qup->irq); + + qup->msg = NULL; + return ret; +} + +static int qup_i2c_wait_for_complete(struct qup_i2c_dev *qup, + struct i2c_msg *msg) { unsigned long left; + int ret = 0; + + left = wait_for_completion_timeout(&qup->xfer, HZ); + if (!left) { + writel(1, qup->base + QUP_SW_RESET); + ret = -ETIMEDOUT; + } + + if (qup->bus_err || qup->qup_err) { + if (qup->bus_err & QUP_I2C_NACK_FLAG) { + dev_err(qup->dev, "NACK from %x\n", msg->addr); + ret = -EIO; + } + } + + return ret; +} + +static int qup_i2c_write_one_v2(struct qup_i2c_dev *qup, struct i2c_msg *msg) +{ + int ret = 0; + + qup->msg = msg; + qup->pos = 0; + enable_irq(qup->irq); + qup_i2c_set_blk_data(qup, msg); + qup_i2c_set_write_mode_v2(qup, msg); + + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto err; + + writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); + + do { + ret = qup_i2c_issue_xfer_v2(qup, msg); + if (ret) + goto err; + + ret = qup_i2c_wait_for_complete(qup, msg); + if (ret) + goto err; + + qup->blk.pos++; + } while (qup->blk.pos < qup->blk.count); + + ret = qup_i2c_wait_ready(qup, QUP_OUT_NOT_EMPTY, RESET_BIT, ONE_BYTE); + +err: + disable_irq(qup->irq); + qup->msg = NULL; + + return ret; +} + +static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) +{ int ret; qup->msg = msg; @@ -325,30 +958,21 @@ static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) if (ret) goto err; - qup_i2c_issue_write(qup, msg); - - ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + ret = qup_i2c_issue_write(qup, msg); if (ret) goto err; - left = wait_for_completion_timeout(&qup->xfer, HZ); - if (!left) { - writel(1, qup->base + QUP_SW_RESET); - ret = -ETIMEDOUT; + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) goto err; - } - if (qup->bus_err || qup->qup_err) { - if (qup->bus_err & QUP_I2C_NACK_FLAG) - dev_err(qup->dev, "NACK from %x\n", msg->addr); - ret = -EIO; + ret = qup_i2c_wait_for_complete(qup, msg); + if (ret) goto err; - } } while (qup->pos < msg->len); /* Wait for the outstanding data in the fifo to drain */ - ret = qup_i2c_wait_writeready(qup); - + ret = qup_i2c_wait_ready(qup, QUP_OUT_NOT_EMPTY, RESET_BIT, ONE_BYTE); err: disable_irq(qup->irq); qup->msg = NULL; @@ -370,6 +994,28 @@ static void qup_i2c_set_read_mode(struct qup_i2c_dev *qup, int len) } } +static void qup_i2c_set_read_mode_v2(struct qup_i2c_dev *qup, int len) +{ + int tx_len = qup->blk.tx_tag_len; + + len += qup->blk.rx_tag_len; + len |= qup->config_run; + tx_len |= qup->config_run; + + if (len < qup->in_fifo_sz) { + /* FIFO mode */ + writel(QUP_REPACK_EN, qup->base + QUP_IO_MODE); + writel(tx_len, qup->base + QUP_MX_WRITE_CNT); + writel(len, qup->base + QUP_MX_READ_CNT); + } else { + /* BLOCK mode (transfer data on chunks) */ + writel(QUP_INPUT_BLK_MODE | QUP_REPACK_EN, + qup->base + QUP_IO_MODE); + writel(tx_len, qup->base + QUP_MX_OUTPUT_CNT); + writel(len, qup->base + QUP_MX_INPUT_CNT); + } +} + static void qup_i2c_issue_read(struct qup_i2c_dev *qup, struct i2c_msg *msg) { u32 addr, len, val; @@ -384,18 +1030,19 @@ static void qup_i2c_issue_read(struct qup_i2c_dev *qup, struct i2c_msg *msg) } -static void qup_i2c_read_fifo(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static int qup_i2c_read_fifo(struct qup_i2c_dev *qup, struct i2c_msg *msg) { - u32 opflags; u32 val = 0; int idx; + int ret = 0; for (idx = 0; qup->pos < msg->len; idx++) { if ((idx & 1) == 0) { /* Check that FIFO have data */ - opflags = readl(qup->base + QUP_OPERATIONAL); - if (!(opflags & QUP_IN_NOT_EMPTY)) - break; + ret = qup_i2c_wait_ready(qup, QUP_IN_NOT_EMPTY, + SET_BIT, 4 * ONE_BYTE); + if (ret) + return ret; /* Reading 2 words at time */ val = readl(qup->base + QUP_IN_FIFO_BASE); @@ -405,18 +1052,94 @@ static void qup_i2c_read_fifo(struct qup_i2c_dev *qup, struct i2c_msg *msg) msg->buf[qup->pos++] = val >> QUP_MSW_SHIFT; } } + + return ret; +} + +static int qup_i2c_read_fifo_v2(struct qup_i2c_dev *qup, + struct i2c_msg *msg) +{ + u32 val; + int idx, pos = 0, ret = 0, total; + + total = qup_i2c_get_data_len(qup); + + /* 2 extra bytes for read tags */ + while (pos < (total + 2)) { + /* Check that FIFO have data */ + ret = qup_i2c_wait_ready(qup, QUP_IN_NOT_EMPTY, + SET_BIT, 4 * ONE_BYTE); + if (ret) { + dev_err(qup->dev, "timeout for fifo not empty"); + return ret; + } + val = readl(qup->base + QUP_IN_FIFO_BASE); + + for (idx = 0; idx < 4; idx++, val >>= 8, pos++) { + /* first 2 bytes are tag bytes */ + if (pos < 2) + continue; + + if (pos >= (total + 2)) + goto out; + + msg->buf[qup->pos++] = val & 0xff; + } + } + +out: + qup->blk.data_len -= total; + + return ret; +} + +static int qup_i2c_read_one_v2(struct qup_i2c_dev *qup, struct i2c_msg *msg) +{ + int ret = 0; + + qup->msg = msg; + qup->pos = 0; + enable_irq(qup->irq); + qup_i2c_set_blk_data(qup, msg); + qup_i2c_set_read_mode_v2(qup, msg->len); + + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto err; + + writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); + + do { + ret = qup_i2c_issue_xfer_v2(qup, msg); + if (ret) + goto err; + + ret = qup_i2c_wait_for_complete(qup, msg); + if (ret) + goto err; + + ret = qup_i2c_read_fifo_v2(qup, msg); + if (ret) + goto err; + + qup->blk.pos++; + } while (qup->blk.pos < qup->blk.count); + +err: + disable_irq(qup->irq); + qup->msg = NULL; + + return ret; } static int qup_i2c_read_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) { - unsigned long left; int ret; qup->msg = msg; qup->pos = 0; enable_irq(qup->irq); - qup_i2c_set_read_mode(qup, msg->len); ret = qup_i2c_change_state(qup, QUP_RUN_STATE); @@ -436,21 +1159,13 @@ static int qup_i2c_read_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) goto err; do { - left = wait_for_completion_timeout(&qup->xfer, HZ); - if (!left) { - writel(1, qup->base + QUP_SW_RESET); - ret = -ETIMEDOUT; + ret = qup_i2c_wait_for_complete(qup, msg); + if (ret) goto err; - } - if (qup->bus_err || qup->qup_err) { - if (qup->bus_err & QUP_I2C_NACK_FLAG) - dev_err(qup->dev, "NACK from %x\n", msg->addr); - ret = -EIO; + ret = qup_i2c_read_fifo(qup, msg); + if (ret) goto err; - } - - qup_i2c_read_fifo(qup, msg); } while (qup->pos < msg->len); err: @@ -513,6 +1228,89 @@ out: return ret; } +static int qup_i2c_xfer_v2(struct i2c_adapter *adap, + struct i2c_msg msgs[], + int num) +{ + struct qup_i2c_dev *qup = i2c_get_adapdata(adap); + int ret, len, idx = 0, use_dma = 0; + + ret = pm_runtime_get_sync(qup->dev); + if (ret < 0) + goto out; + + writel(1, qup->base + QUP_SW_RESET); + ret = qup_i2c_poll_state(qup, QUP_RESET_STATE); + if (ret) + goto out; + + /* Configure QUP as I2C mini core */ + writel(I2C_MINI_CORE | I2C_N_VAL_V2, qup->base + QUP_CONFIG); + writel(QUP_V2_TAGS_EN, qup->base + QUP_I2C_MASTER_GEN); + + if ((qup->is_dma)) { + /* All i2c_msgs should be transferred using either dma or cpu */ + for (idx = 0; idx < num; idx++) { + if (msgs[idx].len == 0) { + ret = -EINVAL; + goto out; + } + + len = (msgs[idx].len > qup->out_fifo_sz) || + (msgs[idx].len > qup->in_fifo_sz); + + if ((!is_vmalloc_addr(msgs[idx].buf)) && len) { + use_dma = 1; + } else { + use_dma = 0; + break; + } + } + } + + idx = 0; + + do { + if (msgs[idx].len == 0) { + ret = -EINVAL; + goto out; + } + + if (qup_i2c_poll_state_i2c_master(qup)) { + ret = -EIO; + goto out; + } + + qup->is_last = (idx == (num - 1)); + if (idx) + qup->config_run = QUP_I2C_MX_CONFIG_DURING_RUN; + else + qup->config_run = 0; + + reinit_completion(&qup->xfer); + + if (use_dma) { + ret = qup_i2c_bam_xfer(adap, &msgs[idx], num); + } else { + if (msgs[idx].flags & I2C_M_RD) + ret = qup_i2c_read_one_v2(qup, &msgs[idx]); + else + ret = qup_i2c_write_one_v2(qup, &msgs[idx]); + } + } while ((idx++ < (num - 1)) && !use_dma && !ret); + + if (!ret) + ret = qup_i2c_change_state(qup, QUP_RESET_STATE); + + if (ret == 0) + ret = num; +out: + pm_runtime_mark_last_busy(qup->dev); + pm_runtime_put_autosuspend(qup->dev); + + return ret; +} + static u32 qup_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); @@ -523,6 +1321,11 @@ static const struct i2c_algorithm qup_i2c_algo = { .functionality = qup_i2c_func, }; +static const struct i2c_algorithm qup_i2c_algo_v2 = { + .master_xfer = qup_i2c_xfer_v2, + .functionality = qup_i2c_func, +}; + /* * The QUP block will issue a NACK and STOP on the bus when reaching * the end of the read, the length of the read is specified as one byte @@ -561,6 +1364,7 @@ static int qup_i2c_probe(struct platform_device *pdev) int ret, fs_div, hs_div; int src_clk_freq; u32 clk_freq = 100000; + int blocks; qup = devm_kzalloc(&pdev->dev, sizeof(*qup), GFP_KERNEL); if (!qup) @@ -572,6 +1376,68 @@ static int qup_i2c_probe(struct platform_device *pdev) of_property_read_u32(node, "clock-frequency", &clk_freq); + if (of_device_is_compatible(pdev->dev.of_node, "qcom,i2c-qup-v1.1.1")) { + qup->adap.algo = &qup_i2c_algo; + qup->adap.quirks = &qup_i2c_quirks; + } else { + qup->adap.algo = &qup_i2c_algo_v2; + ret = qup_i2c_req_dma(qup); + + if (ret == -EPROBE_DEFER) + goto fail_dma; + else if (ret != 0) + goto nodma; + + blocks = (MX_BLOCKS << 1) + 1; + qup->btx.sg = devm_kzalloc(&pdev->dev, + sizeof(*qup->btx.sg) * blocks, + GFP_KERNEL); + if (!qup->btx.sg) { + ret = -ENOMEM; + goto fail_dma; + } + sg_init_table(qup->btx.sg, blocks); + + qup->brx.sg = devm_kzalloc(&pdev->dev, + sizeof(*qup->brx.sg) * blocks, + GFP_KERNEL); + if (!qup->brx.sg) { + ret = -ENOMEM; + goto fail_dma; + } + sg_init_table(qup->brx.sg, blocks); + + /* 2 tag bytes for each block + 5 for start, stop tags */ + size = blocks * 2 + 5; + qup->dpool = dma_pool_create("qup_i2c-dma-pool", &pdev->dev, + size, 4, 0); + + qup->start_tag.start = dma_pool_alloc(qup->dpool, GFP_KERNEL, + &qup->start_tag.addr); + if (!qup->start_tag.start) { + ret = -ENOMEM; + goto fail_dma; + } + + qup->brx.tag.start = dma_pool_alloc(qup->dpool, + GFP_KERNEL, + &qup->brx.tag.addr); + if (!qup->brx.tag.start) { + ret = -ENOMEM; + goto fail_dma; + } + + qup->btx.tag.start = dma_pool_alloc(qup->dpool, + GFP_KERNEL, + &qup->btx.tag.addr); + if (!qup->btx.tag.start) { + ret = -ENOMEM; + goto fail_dma; + } + qup->is_dma = true; + } + +nodma: /* We support frequencies up to FAST Mode (400KHz) */ if (!clk_freq || clk_freq > 400000) { dev_err(qup->dev, "clock frequency not supported %d\n", @@ -667,10 +1533,10 @@ static int qup_i2c_probe(struct platform_device *pdev) qup->out_blk_sz, qup->out_fifo_sz); i2c_set_adapdata(&qup->adap, qup); - qup->adap.algo = &qup_i2c_algo; - qup->adap.quirks = &qup_i2c_quirks; qup->adap.dev.parent = qup->dev; qup->adap.dev.of_node = pdev->dev.of_node; + qup->is_last = true; + strlcpy(qup->adap.name, "QUP I2C adapter", sizeof(qup->adap.name)); pm_runtime_set_autosuspend_delay(qup->dev, MSEC_PER_SEC); @@ -689,6 +1555,11 @@ fail_runtime: pm_runtime_set_suspended(qup->dev); fail: qup_i2c_disable_clocks(qup); +fail_dma: + if (qup->btx.dma) + dma_release_channel(qup->btx.dma); + if (qup->brx.dma) + dma_release_channel(qup->brx.dma); return ret; } @@ -696,6 +1567,18 @@ static int qup_i2c_remove(struct platform_device *pdev) { struct qup_i2c_dev *qup = platform_get_drvdata(pdev); + if (qup->is_dma) { + dma_pool_free(qup->dpool, qup->start_tag.start, + qup->start_tag.addr); + dma_pool_free(qup->dpool, qup->brx.tag.start, + qup->brx.tag.addr); + dma_pool_free(qup->dpool, qup->btx.tag.start, + qup->btx.tag.addr); + dma_pool_destroy(qup->dpool); + dma_release_channel(qup->btx.dma); + dma_release_channel(qup->brx.dma); + } + disable_irq(qup->irq); qup_i2c_disable_clocks(qup); i2c_del_adapter(&qup->adap); diff --git a/drivers/i2c/busses/i2c-rcar.c b/drivers/i2c/busses/i2c-rcar.c index 599c0d7..52407f3 100644 --- a/drivers/i2c/busses/i2c-rcar.c +++ b/drivers/i2c/busses/i2c-rcar.c @@ -1,7 +1,8 @@ /* * Driver for the Renesas RCar I2C unit * - * Copyright (C) 2014 Wolfram Sang + * Copyright (C) 2014-15 Wolfram Sang + * Copyright (C) 2011-2015 Renesas Electronics Corporation * * Copyright (C) 2012-14 Renesas Solutions Corp. * Kuninori Morimoto @@ -9,9 +10,6 @@ * This file is based on the drivers/i2c/busses/i2c-sh7760.c * (c) 2005-2008 MSC Vertriebsges.m.b.H, Manuel Lauss * - * This file used out-of-tree driver i2c-rcar.c - * Copyright (C) 2011-2012 Renesas Electronics 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; version 2 of the License. @@ -23,6 +21,8 @@ */ #include #include +#include +#include #include #include #include @@ -33,7 +33,6 @@ #include #include #include -#include /* register offsets */ #define ICSCR 0x00 /* slave ctrl */ @@ -46,6 +45,8 @@ #define ICSAR 0x1C /* slave address */ #define ICMAR 0x20 /* master address */ #define ICRXTX 0x24 /* data port */ +#define ICDMAER 0x3c /* DMA enable */ +#define ICFBSCR 0x38 /* first bit setup cycle */ /* ICSCR */ #define SDBS (1 << 3) /* slave data buffer select */ @@ -81,9 +82,20 @@ #define MDR (1 << 1) #define MAT (1 << 0) /* slave addr xfer done */ +/* ICDMAER */ +#define RSDMAE (1 << 3) /* DMA Slave Received Enable */ +#define TSDMAE (1 << 2) /* DMA Slave Transmitted Enable */ +#define RMDMAE (1 << 1) /* DMA Master Received Enable */ +#define TMDMAE (1 << 0) /* DMA Master Transmitted Enable */ + +/* ICFBSCR */ +#define TCYC06 0x04 /* 6*Tcyc delay 1st bit between SDA and SCL */ +#define TCYC17 0x0f /* 17*Tcyc delay 1st bit between SDA and SCL */ + #define RCAR_BUS_PHASE_START (MDBS | MIE | ESG) #define RCAR_BUS_PHASE_DATA (MDBS | MIE) +#define RCAR_BUS_MASK_DATA (~(ESG | FSB) & 0xFF) #define RCAR_BUS_PHASE_STOP (MDBS | MIE | FSB) #define RCAR_IRQ_SEND (MNR | MAL | MST | MAT | MDE) @@ -94,10 +106,13 @@ #define RCAR_IRQ_ACK_RECV (~(MAT | MDR) & 0xFF) #define ID_LAST_MSG (1 << 0) -#define ID_IOERROR (1 << 1) +#define ID_FIRST_MSG (1 << 1) #define ID_DONE (1 << 2) #define ID_ARBLOST (1 << 3) #define ID_NACK (1 << 4) +/* persistent flags */ +#define ID_P_PM_BLOCKED (1 << 31) +#define ID_P_MASK ID_P_PM_BLOCKED enum rcar_i2c_type { I2C_RCAR_GEN1, @@ -108,10 +123,10 @@ enum rcar_i2c_type { struct rcar_i2c_priv { void __iomem *io; struct i2c_adapter adap; - struct i2c_msg *msg; + struct i2c_msg *msg; + int msgs_left; struct clk *clk; - spinlock_t lock; wait_queue_head_t wait; int pos; @@ -119,14 +134,17 @@ struct rcar_i2c_priv { u32 flags; enum rcar_i2c_type devtype; struct i2c_client *slave; + + struct resource *res; + struct dma_chan *dma_tx; + struct dma_chan *dma_rx; + struct scatterlist sg; + enum dma_data_direction dma_direction; }; #define rcar_i2c_priv_to_dev(p) ((p)->adap.dev.parent) #define rcar_i2c_is_recv(p) ((p)->msg->flags & I2C_M_RD) -#define rcar_i2c_flags_set(p, f) ((p)->flags |= (f)) -#define rcar_i2c_flags_has(p, f) ((p)->flags & (f)) - #define LOOP_TIMEOUT 1024 @@ -144,9 +162,10 @@ static void rcar_i2c_init(struct rcar_i2c_priv *priv) { /* reset master mode */ rcar_i2c_write(priv, ICMIER, 0); - rcar_i2c_write(priv, ICMCR, 0); + rcar_i2c_write(priv, ICMCR, MDBS); rcar_i2c_write(priv, ICMSR, 0); - rcar_i2c_write(priv, ICMAR, 0); + /* start clock */ + rcar_i2c_write(priv, ICCCR, priv->icccr); } static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv) @@ -163,15 +182,17 @@ static int rcar_i2c_bus_barrier(struct rcar_i2c_priv *priv) return -EBUSY; } -static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, - u32 bus_speed, - struct device *dev) +static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, struct i2c_timings *t) { - u32 scgd, cdf; - u32 round, ick; - u32 scl; - u32 cdf_width; + u32 scgd, cdf, round, ick, sum, scl, cdf_width; unsigned long rate; + struct device *dev = rcar_i2c_priv_to_dev(priv); + + /* Fall back to previously used values if not supplied */ + t->bus_freq_hz = t->bus_freq_hz ?: 100000; + t->scl_fall_ns = t->scl_fall_ns ?: 35; + t->scl_rise_ns = t->scl_rise_ns ?: 200; + t->scl_int_delay_ns = t->scl_int_delay_ns ?: 50; switch (priv->devtype) { case I2C_RCAR_GEN1: @@ -195,9 +216,9 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, * SCL = ick / (20 + SCGD * 8 + F[(ticf + tr + intd) * ick]) * * ick : I2C internal clock < 20 MHz - * ticf : I2C SCL falling time = 35 ns here - * tr : I2C SCL rising time = 200 ns here - * intd : LSI internal delay = 50 ns here + * ticf : I2C SCL falling time + * tr : I2C SCL rising time + * intd : LSI internal delay * clkp : peripheral_clk * F[] : integer up-valuation */ @@ -213,12 +234,12 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, * it is impossible to calculate large scale * number on u32. separate it * - * F[(ticf + tr + intd) * ick] - * = F[(35 + 200 + 50)ns * ick] - * = F[285 * ick / 1000000000] - * = F[(ick / 1000000) * 285 / 1000] + * F[(ticf + tr + intd) * ick] with sum = (ticf + tr + intd) + * = F[sum * ick / 1000000000] + * = F[(ick / 1000000) * sum / 1000] */ - round = (ick + 500000) / 1000000 * 285; + sum = t->scl_fall_ns + t->scl_rise_ns + t->scl_int_delay_ns; + round = (ick + 500000) / 1000000 * sum; round = (round + 500) / 1000; /* @@ -235,7 +256,7 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, */ for (scgd = 0; scgd < 0x40; scgd++) { scl = ick / (20 + (scgd * 8) + round); - if (scl <= bus_speed) + if (scl <= t->bus_freq_hz) goto scgd_find; } dev_err(dev, "it is impossible to calculate best SCL\n"); @@ -243,11 +264,9 @@ static int rcar_i2c_clock_calculate(struct rcar_i2c_priv *priv, scgd_find: dev_dbg(dev, "clk %d/%d(%lu), round %u, CDF:0x%x, SCGD: 0x%x\n", - scl, bus_speed, clk_get_rate(priv->clk), round, cdf, scgd); + scl, t->bus_freq_hz, clk_get_rate(priv->clk), round, cdf, scgd); - /* - * keep icccr value - */ + /* keep icccr value */ priv->icccr = scgd << cdf_width | cdf; return 0; @@ -257,33 +276,156 @@ static void rcar_i2c_prepare_msg(struct rcar_i2c_priv *priv) { int read = !!rcar_i2c_is_recv(priv); + priv->pos = 0; + if (priv->msgs_left == 1) + priv->flags |= ID_LAST_MSG; + rcar_i2c_write(priv, ICMAR, (priv->msg->addr << 1) | read); - rcar_i2c_write(priv, ICMSR, 0); - rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START); + /* + * We don't have a testcase but the HW engineers say that the write order + * of ICMSR and ICMCR depends on whether we issue START or REP_START. Since + * it didn't cause a drawback for me, let's rather be safe than sorry. + */ + if (priv->flags & ID_FIRST_MSG) { + rcar_i2c_write(priv, ICMSR, 0); + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START); + } else { + rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_START); + rcar_i2c_write(priv, ICMSR, 0); + } rcar_i2c_write(priv, ICMIER, read ? RCAR_IRQ_RECV : RCAR_IRQ_SEND); } +static void rcar_i2c_next_msg(struct rcar_i2c_priv *priv) +{ + priv->msg++; + priv->msgs_left--; + priv->flags &= ID_P_MASK; + rcar_i2c_prepare_msg(priv); +} + /* * interrupt functions */ -static int rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) +static void rcar_i2c_dma_unmap(struct rcar_i2c_priv *priv) +{ + struct dma_chan *chan = priv->dma_direction == DMA_FROM_DEVICE + ? priv->dma_rx : priv->dma_tx; + + /* Disable DMA Master Received/Transmitted */ + rcar_i2c_write(priv, ICDMAER, 0); + + /* Reset default delay */ + rcar_i2c_write(priv, ICFBSCR, TCYC06); + + dma_unmap_single(chan->device->dev, sg_dma_address(&priv->sg), + priv->msg->len, priv->dma_direction); + + priv->dma_direction = DMA_NONE; +} + +static void rcar_i2c_cleanup_dma(struct rcar_i2c_priv *priv) +{ + if (priv->dma_direction == DMA_NONE) + return; + else if (priv->dma_direction == DMA_FROM_DEVICE) + dmaengine_terminate_all(priv->dma_rx); + else if (priv->dma_direction == DMA_TO_DEVICE) + dmaengine_terminate_all(priv->dma_tx); + + rcar_i2c_dma_unmap(priv); +} + +static void rcar_i2c_dma_callback(void *data) +{ + struct rcar_i2c_priv *priv = data; + + priv->pos += sg_dma_len(&priv->sg); + + rcar_i2c_dma_unmap(priv); +} + +static void rcar_i2c_dma(struct rcar_i2c_priv *priv) { + struct device *dev = rcar_i2c_priv_to_dev(priv); struct i2c_msg *msg = priv->msg; + bool read = msg->flags & I2C_M_RD; + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + struct dma_chan *chan = read ? priv->dma_rx : priv->dma_tx; + struct dma_async_tx_descriptor *txdesc; + dma_addr_t dma_addr; + dma_cookie_t cookie; + unsigned char *buf; + int len; + + /* Do not use DMA if it's not available or for messages < 8 bytes */ + if (IS_ERR(chan) || msg->len < 8) + return; + + if (read) { + /* + * The last two bytes needs to be fetched using PIO in + * order for the STOP phase to work. + */ + buf = priv->msg->buf; + len = priv->msg->len - 2; + } else { + /* + * First byte in message was sent using PIO. + */ + buf = priv->msg->buf + 1; + len = priv->msg->len - 1; + } - /* - * FIXME - * sometimes, unknown interrupt happened. - * Do nothing - */ - if (!(msr & MDE)) - return 0; + dma_addr = dma_map_single(chan->device->dev, buf, len, dir); + if (dma_mapping_error(dev, dma_addr)) { + dev_dbg(dev, "dma map failed, using PIO\n"); + return; + } - /* - * If address transfer phase finished, - * goto data phase. - */ - if (msr & MAT) - rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); + sg_dma_len(&priv->sg) = len; + sg_dma_address(&priv->sg) = dma_addr; + + priv->dma_direction = dir; + + txdesc = dmaengine_prep_slave_sg(chan, &priv->sg, 1, + read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!txdesc) { + dev_dbg(dev, "dma prep slave sg failed, using PIO\n"); + rcar_i2c_cleanup_dma(priv); + return; + } + + txdesc->callback = rcar_i2c_dma_callback; + txdesc->callback_param = priv; + + cookie = dmaengine_submit(txdesc); + if (dma_submit_error(cookie)) { + dev_dbg(dev, "submitting dma failed, using PIO\n"); + rcar_i2c_cleanup_dma(priv); + return; + } + + /* Set delay for DMA operations */ + rcar_i2c_write(priv, ICFBSCR, TCYC17); + + /* Enable DMA Master Received/Transmitted */ + if (read) + rcar_i2c_write(priv, ICDMAER, RMDMAE); + else + rcar_i2c_write(priv, ICDMAER, TMDMAE); + + dma_async_issue_pending(chan); +} + +static void rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) +{ + struct i2c_msg *msg = priv->msg; + + /* FIXME: sometimes, unknown interrupt happened. Do nothing */ + if (!(msr & MDE)) + return; if (priv->pos < msg->len) { /* @@ -296,6 +438,12 @@ static int rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) rcar_i2c_write(priv, ICRXTX, msg->buf[priv->pos]); priv->pos++; + /* + * Try to use DMA to transmit the rest of the data if + * address transfer pashe just finished. + */ + if (msr & MAT) + rcar_i2c_dma(priv); } else { /* * The last data was pushed to ICRXTX on _PREV_ empty irq. @@ -305,67 +453,54 @@ static int rcar_i2c_irq_send(struct rcar_i2c_priv *priv, u32 msr) * [ICRXTX] -> [SHIFT] -> [I2C bus] */ - if (priv->flags & ID_LAST_MSG) + if (priv->flags & ID_LAST_MSG) { /* * If current msg is the _LAST_ msg, * prepare stop condition here. * ID_DONE will be set on STOP irq. */ rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); - else - /* - * If current msg is _NOT_ last msg, - * it doesn't call stop phase. - * thus, there is no STOP irq. - * return ID_DONE here. - */ - return ID_DONE; + } else { + rcar_i2c_next_msg(priv); + return; + } } rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_SEND); - - return 0; } -static int rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr) +static void rcar_i2c_irq_recv(struct rcar_i2c_priv *priv, u32 msr) { struct i2c_msg *msg = priv->msg; - /* - * FIXME - * sometimes, unknown interrupt happened. - * Do nothing - */ + /* FIXME: sometimes, unknown interrupt happened. Do nothing */ if (!(msr & MDR)) - return 0; + return; if (msr & MAT) { /* - * Address transfer phase finished, - * but, there is no data at this point. - * Do nothing. + * Address transfer phase finished, but no data at this point. + * Try to use DMA to receive data. */ + rcar_i2c_dma(priv); } else if (priv->pos < msg->len) { - /* - * get received data - */ + /* get received data */ msg->buf[priv->pos] = rcar_i2c_read(priv, ICRXTX); priv->pos++; } /* - * If next received data is the _LAST_, - * go to STOP phase, - * otherwise, go to DATA phase. + * If next received data is the _LAST_, go to STOP phase. Might be + * overwritten by REP START when setting up a new msg. Not elegant + * but the only stable sequence for REP START I have found so far. */ if (priv->pos + 1 >= msg->len) rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); - else - rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_DATA); - - rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_RECV); - return 0; + if (priv->pos == msg->len && !(priv->flags & ID_LAST_MSG)) + rcar_i2c_next_msg(priv); + else + rcar_i2c_write(priv, ICMSR, RCAR_IRQ_ACK_RECV); } static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv) @@ -426,62 +561,132 @@ static bool rcar_i2c_slave_irq(struct rcar_i2c_priv *priv) static irqreturn_t rcar_i2c_irq(int irq, void *ptr) { struct rcar_i2c_priv *priv = ptr; - irqreturn_t result = IRQ_HANDLED; - u32 msr; - - /*-------------- spin lock -----------------*/ - spin_lock(&priv->lock); + u32 msr, val; - if (rcar_i2c_slave_irq(priv)) - goto exit; + /* Clear START or STOP as soon as we can */ + val = rcar_i2c_read(priv, ICMCR); + rcar_i2c_write(priv, ICMCR, val & RCAR_BUS_MASK_DATA); msr = rcar_i2c_read(priv, ICMSR); /* Only handle interrupts that are currently enabled */ msr &= rcar_i2c_read(priv, ICMIER); if (!msr) { - result = IRQ_NONE; - goto exit; + if (rcar_i2c_slave_irq(priv)) + return IRQ_HANDLED; + + return IRQ_NONE; } /* Arbitration lost */ if (msr & MAL) { - rcar_i2c_flags_set(priv, (ID_DONE | ID_ARBLOST)); + priv->flags |= ID_DONE | ID_ARBLOST; goto out; } /* Nack */ if (msr & MNR) { - /* go to stop phase */ - rcar_i2c_write(priv, ICMCR, RCAR_BUS_PHASE_STOP); + /* HW automatically sends STOP after received NACK */ rcar_i2c_write(priv, ICMIER, RCAR_IRQ_STOP); - rcar_i2c_flags_set(priv, ID_NACK); + priv->flags |= ID_NACK; goto out; } /* Stop */ if (msr & MST) { - rcar_i2c_flags_set(priv, ID_DONE); + priv->msgs_left--; /* The last message also made it */ + priv->flags |= ID_DONE; goto out; } if (rcar_i2c_is_recv(priv)) - rcar_i2c_flags_set(priv, rcar_i2c_irq_recv(priv, msr)); + rcar_i2c_irq_recv(priv, msr); else - rcar_i2c_flags_set(priv, rcar_i2c_irq_send(priv, msr)); + rcar_i2c_irq_send(priv, msr); out: - if (rcar_i2c_flags_has(priv, ID_DONE)) { + if (priv->flags & ID_DONE) { rcar_i2c_write(priv, ICMIER, 0); rcar_i2c_write(priv, ICMSR, 0); wake_up(&priv->wait); } -exit: - spin_unlock(&priv->lock); - /*-------------- spin unlock -----------------*/ + return IRQ_HANDLED; +} + +static struct dma_chan *rcar_i2c_request_dma_chan(struct device *dev, + enum dma_transfer_direction dir, + dma_addr_t port_addr) +{ + struct dma_chan *chan; + struct dma_slave_config cfg; + char *chan_name = dir == DMA_MEM_TO_DEV ? "tx" : "rx"; + int ret; + + chan = dma_request_chan(dev, chan_name); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + dev_dbg(dev, "request_channel failed for %s (%d)\n", + chan_name, ret); + return chan; + } + + memset(&cfg, 0, sizeof(cfg)); + cfg.direction = dir; + if (dir == DMA_MEM_TO_DEV) { + cfg.dst_addr = port_addr; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + } else { + cfg.src_addr = port_addr; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; + } + + ret = dmaengine_slave_config(chan, &cfg); + if (ret) { + dev_dbg(dev, "slave_config failed for %s (%d)\n", + chan_name, ret); + dma_release_channel(chan); + return ERR_PTR(ret); + } + + dev_dbg(dev, "got DMA channel for %s\n", chan_name); + return chan; +} + +static void rcar_i2c_request_dma(struct rcar_i2c_priv *priv, + struct i2c_msg *msg) +{ + struct device *dev = rcar_i2c_priv_to_dev(priv); + bool read; + struct dma_chan *chan; + enum dma_transfer_direction dir; + + read = msg->flags & I2C_M_RD; + + chan = read ? priv->dma_rx : priv->dma_tx; + if (PTR_ERR(chan) != -EPROBE_DEFER) + return; + + dir = read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + chan = rcar_i2c_request_dma_chan(dev, dir, priv->res->start + ICRXTX); - return result; + if (read) + priv->dma_rx = chan; + else + priv->dma_tx = chan; +} + +static void rcar_i2c_release_dma(struct rcar_i2c_priv *priv) +{ + if (!IS_ERR(priv->dma_tx)) { + dma_release_channel(priv->dma_tx); + priv->dma_tx = ERR_PTR(-EPROBE_DEFER); + } + + if (!IS_ERR(priv->dma_rx)) { + dma_release_channel(priv->dma_rx); + priv->dma_rx = ERR_PTR(-EPROBE_DEFER); + } } static int rcar_i2c_master_xfer(struct i2c_adapter *adap, @@ -490,22 +695,11 @@ static int rcar_i2c_master_xfer(struct i2c_adapter *adap, { struct rcar_i2c_priv *priv = i2c_get_adapdata(adap); struct device *dev = rcar_i2c_priv_to_dev(priv); - unsigned long flags; int i, ret; - long timeout; + long time_left; pm_runtime_get_sync(dev); - /*-------------- spin lock -----------------*/ - spin_lock_irqsave(&priv->lock, flags); - - rcar_i2c_init(priv); - /* start clock */ - rcar_i2c_write(priv, ICCCR, priv->icccr); - - spin_unlock_irqrestore(&priv->lock, flags); - /*-------------- spin unlock -----------------*/ - ret = rcar_i2c_bus_barrier(priv); if (ret < 0) goto out; @@ -514,48 +708,29 @@ static int rcar_i2c_master_xfer(struct i2c_adapter *adap, /* This HW can't send STOP after address phase */ if (msgs[i].len == 0) { ret = -EOPNOTSUPP; - break; - } - - /*-------------- spin lock -----------------*/ - spin_lock_irqsave(&priv->lock, flags); - - /* init each data */ - priv->msg = &msgs[i]; - priv->pos = 0; - priv->flags = 0; - if (i == num - 1) - rcar_i2c_flags_set(priv, ID_LAST_MSG); - - rcar_i2c_prepare_msg(priv); - - spin_unlock_irqrestore(&priv->lock, flags); - /*-------------- spin unlock -----------------*/ - - timeout = wait_event_timeout(priv->wait, - rcar_i2c_flags_has(priv, ID_DONE), - adap->timeout); - if (!timeout) { - ret = -ETIMEDOUT; - break; - } - - if (rcar_i2c_flags_has(priv, ID_NACK)) { - ret = -ENXIO; - break; - } - - if (rcar_i2c_flags_has(priv, ID_ARBLOST)) { - ret = -EAGAIN; - break; - } - - if (rcar_i2c_flags_has(priv, ID_IOERROR)) { - ret = -EIO; - break; + goto out; } + rcar_i2c_request_dma(priv, msgs + i); + } - ret = i + 1; /* The number of transfer */ + /* init first message */ + priv->msg = msgs; + priv->msgs_left = num; + priv->flags = (priv->flags & ID_P_MASK) | ID_FIRST_MSG; + rcar_i2c_prepare_msg(priv); + + time_left = wait_event_timeout(priv->wait, priv->flags & ID_DONE, + num * adap->timeout); + if (!time_left) { + rcar_i2c_cleanup_dma(priv); + rcar_i2c_init(priv); + ret = -ETIMEDOUT; + } else if (priv->flags & ID_NACK) { + ret = -ENXIO; + } else if (priv->flags & ID_ARBLOST) { + ret = -EAGAIN; + } else { + ret = num - priv->msgs_left; /* The number of transfer */ } out: pm_runtime_put(dev); @@ -635,9 +810,8 @@ static int rcar_i2c_probe(struct platform_device *pdev) { struct rcar_i2c_priv *priv; struct i2c_adapter *adap; - struct resource *res; struct device *dev = &pdev->dev; - u32 bus_speed; + struct i2c_timings i2c_t; int irq, ret; priv = devm_kzalloc(dev, sizeof(struct rcar_i2c_priv), GFP_KERNEL); @@ -650,23 +824,14 @@ static int rcar_i2c_probe(struct platform_device *pdev) return PTR_ERR(priv->clk); } - bus_speed = 100000; /* default 100 kHz */ - of_property_read_u32(dev->of_node, "clock-frequency", &bus_speed); - - priv->devtype = (enum rcar_i2c_type)of_match_device(rcar_i2c_dt_ids, dev)->data; - - ret = rcar_i2c_clock_calculate(priv, bus_speed, dev); - if (ret < 0) - return ret; + priv->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->io = devm_ioremap_resource(dev, res); + priv->io = devm_ioremap_resource(dev, priv->res); if (IS_ERR(priv->io)) return PTR_ERR(priv->io); - irq = platform_get_irq(pdev, 0); + priv->devtype = (enum rcar_i2c_type)of_device_get_match_data(dev); init_waitqueue_head(&priv->wait); - spin_lock_init(&priv->lock); adap = &priv->adap; adap->nr = pdev->id; @@ -678,26 +843,52 @@ static int rcar_i2c_probe(struct platform_device *pdev) i2c_set_adapdata(adap, priv); strlcpy(adap->name, pdev->name, sizeof(adap->name)); - ret = devm_request_irq(dev, irq, rcar_i2c_irq, 0, - dev_name(dev), priv); + i2c_parse_fw_timings(dev, &i2c_t, false); + + /* Init DMA */ + sg_init_table(&priv->sg, 1); + priv->dma_direction = DMA_NONE; + priv->dma_rx = priv->dma_tx = ERR_PTR(-EPROBE_DEFER); + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + ret = rcar_i2c_clock_calculate(priv, &i2c_t); + if (ret < 0) + goto out_pm_put; + + rcar_i2c_init(priv); + + /* Don't suspend when multi-master to keep arbitration working */ + if (of_property_read_bool(dev->of_node, "multi-master")) + priv->flags |= ID_P_PM_BLOCKED; + else + pm_runtime_put(dev); + + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(dev, irq, rcar_i2c_irq, 0, dev_name(dev), priv); if (ret < 0) { dev_err(dev, "cannot get irq %d\n", irq); - return ret; + goto out_pm_disable; } - pm_runtime_enable(dev); platform_set_drvdata(pdev, priv); ret = i2c_add_numbered_adapter(adap); if (ret < 0) { dev_err(dev, "reg adap failed: %d\n", ret); - pm_runtime_disable(dev); - return ret; + goto out_pm_disable; } dev_info(dev, "probed\n"); return 0; + + out_pm_put: + pm_runtime_put(dev); + out_pm_disable: + pm_runtime_disable(dev); + return ret; } static int rcar_i2c_remove(struct platform_device *pdev) @@ -706,6 +897,9 @@ static int rcar_i2c_remove(struct platform_device *pdev) struct device *dev = &pdev->dev; i2c_del_adapter(&priv->adap); + rcar_i2c_release_dma(priv); + if (priv->flags & ID_P_PM_BLOCKED) + pm_runtime_put(dev); pm_runtime_disable(dev); return 0; diff --git a/drivers/i2c/busses/i2c-rk3x.c b/drivers/i2c/busses/i2c-rk3x.c index 9096d17..80bed02 100644 --- a/drivers/i2c/busses/i2c-rk3x.c +++ b/drivers/i2c/busses/i2c-rk3x.c @@ -101,10 +101,7 @@ struct rk3x_i2c { struct notifier_block clk_rate_nb; /* Settings */ - unsigned int scl_frequency; - unsigned int scl_rise_ns; - unsigned int scl_fall_ns; - unsigned int sda_fall_ns; + struct i2c_timings t; /* Synchronization & notification */ spinlock_t lock; @@ -437,10 +434,7 @@ out: * Calculate divider values for desired SCL frequency * * @clk_rate: I2C input clock rate - * @scl_rate: Desired SCL rate - * @scl_rise_ns: How many ns it takes for SCL to rise. - * @scl_fall_ns: How many ns it takes for SCL to fall. - * @sda_fall_ns: How many ns it takes for SDA to fall. + * @t: Known I2C timing information. * @div_low: Divider output for low * @div_high: Divider output for high * @@ -448,11 +442,10 @@ out: * a best-effort divider value is returned in divs. If the target rate is * too high, we silently use the highest possible rate. */ -static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, - unsigned long scl_rise_ns, - unsigned long scl_fall_ns, - unsigned long sda_fall_ns, - unsigned long *div_low, unsigned long *div_high) +static int rk3x_i2c_calc_divs(unsigned long clk_rate, + struct i2c_timings *t, + unsigned long *div_low, + unsigned long *div_high) { unsigned long spec_min_low_ns, spec_min_high_ns; unsigned long spec_setup_start, spec_max_data_hold_ns; @@ -472,12 +465,12 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, int ret = 0; /* Only support standard-mode and fast-mode */ - if (WARN_ON(scl_rate > 400000)) - scl_rate = 400000; + if (WARN_ON(t->bus_freq_hz > 400000)) + t->bus_freq_hz = 400000; /* prevent scl_rate_khz from becoming 0 */ - if (WARN_ON(scl_rate < 1000)) - scl_rate = 1000; + if (WARN_ON(t->bus_freq_hz < 1000)) + t->bus_freq_hz = 1000; /* * min_low_ns: The minimum number of ns we need to hold low to @@ -491,7 +484,7 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, * This is because the i2c host on Rockchip holds the data line * for half the low time. */ - if (scl_rate <= 100000) { + if (t->bus_freq_hz <= 100000) { /* Standard-mode */ spec_min_low_ns = 4700; spec_setup_start = 4700; @@ -506,7 +499,7 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, spec_max_data_hold_ns = 900; data_hold_buffer_ns = 50; } - min_high_ns = scl_rise_ns + spec_min_high_ns; + min_high_ns = t->scl_rise_ns + spec_min_high_ns; /* * Timings for repeated start: @@ -517,18 +510,18 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, * we meet tSU;STA and tHD;STA times. */ min_high_ns = max(min_high_ns, - DIV_ROUND_UP((scl_rise_ns + spec_setup_start) * 1000, 875)); + DIV_ROUND_UP((t->scl_rise_ns + spec_setup_start) * 1000, 875)); min_high_ns = max(min_high_ns, - DIV_ROUND_UP((scl_rise_ns + spec_setup_start + - sda_fall_ns + spec_min_high_ns), 2)); + DIV_ROUND_UP((t->scl_rise_ns + spec_setup_start + + t->sda_fall_ns + spec_min_high_ns), 2)); - min_low_ns = scl_fall_ns + spec_min_low_ns; + min_low_ns = t->scl_fall_ns + spec_min_low_ns; max_low_ns = spec_max_data_hold_ns * 2 - data_hold_buffer_ns; min_total_ns = min_low_ns + min_high_ns; /* Adjust to avoid overflow */ clk_rate_khz = DIV_ROUND_UP(clk_rate, 1000); - scl_rate_khz = scl_rate / 1000; + scl_rate_khz = t->bus_freq_hz / 1000; /* * We need the total div to be >= this number @@ -616,14 +609,13 @@ static int rk3x_i2c_calc_divs(unsigned long clk_rate, unsigned long scl_rate, static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate) { + struct i2c_timings *t = &i2c->t; unsigned long div_low, div_high; u64 t_low_ns, t_high_ns; int ret; - ret = rk3x_i2c_calc_divs(clk_rate, i2c->scl_frequency, i2c->scl_rise_ns, - i2c->scl_fall_ns, i2c->sda_fall_ns, - &div_low, &div_high); - WARN_ONCE(ret != 0, "Could not reach SCL freq %u", i2c->scl_frequency); + ret = rk3x_i2c_calc_divs(clk_rate, t, &div_low, &div_high); + WARN_ONCE(ret != 0, "Could not reach SCL freq %u", t->bus_freq_hz); clk_enable(i2c->clk); i2c_writel(i2c, (div_high << 16) | (div_low & 0xffff), REG_CLKDIV); @@ -634,7 +626,7 @@ static void rk3x_i2c_adapt_div(struct rk3x_i2c *i2c, unsigned long clk_rate) dev_dbg(i2c->dev, "CLK %lukhz, Req %uns, Act low %lluns high %lluns\n", clk_rate / 1000, - 1000000000 / i2c->scl_frequency, + 1000000000 / t->bus_freq_hz, t_low_ns, t_high_ns); } @@ -664,9 +656,7 @@ static int rk3x_i2c_clk_notifier_cb(struct notifier_block *nb, unsigned long switch (event) { case PRE_RATE_CHANGE: - if (rk3x_i2c_calc_divs(ndata->new_rate, i2c->scl_frequency, - i2c->scl_rise_ns, i2c->scl_fall_ns, - i2c->sda_fall_ns, + if (rk3x_i2c_calc_divs(ndata->new_rate, &i2c->t, &div_low, &div_high) != 0) return NOTIFY_STOP; @@ -855,6 +845,7 @@ static struct rk3x_i2c_soc_data soc_data[3] = { static const struct of_device_id rk3x_i2c_match[] = { { .compatible = "rockchip,rk3066-i2c", .data = (void *)&soc_data[0] }, { .compatible = "rockchip,rk3188-i2c", .data = (void *)&soc_data[1] }, + { .compatible = "rockchip,rk3228-i2c", .data = (void *)&soc_data[2] }, { .compatible = "rockchip,rk3288-i2c", .data = (void *)&soc_data[2] }, {}, }; @@ -879,37 +870,8 @@ static int rk3x_i2c_probe(struct platform_device *pdev) match = of_match_node(rk3x_i2c_match, np); i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data; - if (of_property_read_u32(pdev->dev.of_node, "clock-frequency", - &i2c->scl_frequency)) { - dev_info(&pdev->dev, "using default SCL frequency: %d\n", - DEFAULT_SCL_RATE); - i2c->scl_frequency = DEFAULT_SCL_RATE; - } - - if (i2c->scl_frequency == 0 || i2c->scl_frequency > 400 * 1000) { - dev_warn(&pdev->dev, "invalid SCL frequency specified.\n"); - dev_warn(&pdev->dev, "using default SCL frequency: %d\n", - DEFAULT_SCL_RATE); - i2c->scl_frequency = DEFAULT_SCL_RATE; - } - - /* - * Read rise and fall time from device tree. If not available use - * the default maximum timing from the specification. - */ - if (of_property_read_u32(pdev->dev.of_node, "i2c-scl-rising-time-ns", - &i2c->scl_rise_ns)) { - if (i2c->scl_frequency <= 100000) - i2c->scl_rise_ns = 1000; - else - i2c->scl_rise_ns = 300; - } - if (of_property_read_u32(pdev->dev.of_node, "i2c-scl-falling-time-ns", - &i2c->scl_fall_ns)) - i2c->scl_fall_ns = 300; - if (of_property_read_u32(pdev->dev.of_node, "i2c-sda-falling-time-ns", - &i2c->sda_fall_ns)) - i2c->sda_fall_ns = i2c->scl_fall_ns; + /* use common interface to get I2C timing properties */ + i2c_parse_fw_timings(&pdev->dev, &i2c->t, true); strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name)); i2c->adap.owner = THIS_MODULE; diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index 5df8196..38dc1ca 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -163,15 +163,14 @@ static const struct of_device_id s3c24xx_i2c_match[] = { MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match); #endif -/* s3c24xx_get_device_quirks - * +/* * Get controller type either from device tree or platform device variant. -*/ - + */ static inline kernel_ulong_t s3c24xx_get_device_quirks(struct platform_device *pdev) { if (pdev->dev.of_node) { const struct of_device_id *match; + match = of_match_node(s3c24xx_i2c_match, pdev->dev.of_node); return (kernel_ulong_t)match->data; } @@ -179,12 +178,10 @@ static inline kernel_ulong_t s3c24xx_get_device_quirks(struct platform_device *p return platform_get_device_id(pdev)->driver_data; } -/* s3c24xx_i2c_master_complete - * - * complete the message and wake up the caller, using the given return code, +/* + * Complete the message and wake up the caller, using the given return code, * or zero to mean ok. -*/ - + */ static inline void s3c24xx_i2c_master_complete(struct s3c24xx_i2c *i2c, int ret) { dev_dbg(i2c->dev, "master_complete %d\n", ret); @@ -217,7 +214,6 @@ static inline void s3c24xx_i2c_enable_ack(struct s3c24xx_i2c *i2c) } /* irq enable/disable functions */ - static inline void s3c24xx_i2c_disable_irq(struct s3c24xx_i2c *i2c) { unsigned long tmp; @@ -251,11 +247,9 @@ static bool is_ack(struct s3c24xx_i2c *i2c) return false; } -/* s3c24xx_i2c_message_start - * +/* * put the start of a message onto the bus -*/ - + */ static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg) { @@ -284,9 +278,10 @@ static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr); writeb(addr, i2c->regs + S3C2410_IICDS); - /* delay here to ensure the data byte has gotten onto the bus - * before the transaction is started */ - + /* + * delay here to ensure the data byte has gotten onto the bus + * before the transaction is started + */ ndelay(i2c->tx_setup); dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); @@ -361,50 +356,46 @@ static inline void s3c24xx_i2c_stop(struct s3c24xx_i2c *i2c, int ret) s3c24xx_i2c_disable_irq(i2c); } -/* helper functions to determine the current state in the set of - * messages we are sending */ +/* + * helper functions to determine the current state in the set of + * messages we are sending + */ -/* is_lastmsg() - * +/* * returns TRUE if the current message is the last in the set -*/ - + */ static inline int is_lastmsg(struct s3c24xx_i2c *i2c) { return i2c->msg_idx >= (i2c->msg_num - 1); } -/* is_msglast - * +/* * returns TRUE if we this is the last byte in the current message -*/ - + */ static inline int is_msglast(struct s3c24xx_i2c *i2c) { - /* msg->len is always 1 for the first byte of smbus block read. + /* + * msg->len is always 1 for the first byte of smbus block read. * Actual length will be read from slave. More bytes will be - * read according to the length then. */ + * read according to the length then. + */ if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1) return 0; return i2c->msg_ptr == i2c->msg->len-1; } -/* is_msgend - * +/* * returns TRUE if we reached the end of the current message -*/ - + */ static inline int is_msgend(struct s3c24xx_i2c *i2c) { return i2c->msg_ptr >= i2c->msg->len; } -/* i2c_s3c_irq_nextbyte - * +/* * process an interrupt and work out what to do */ - static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) { unsigned long tmp; @@ -423,14 +414,13 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) goto out_ack; case STATE_START: - /* last thing we did was send a start condition on the + /* + * last thing we did was send a start condition on the * bus, or started a new i2c message */ - if (iicstat & S3C2410_IICSTAT_LASTBIT && !(i2c->msg->flags & I2C_M_IGNORE_NAK)) { /* ack was not received... */ - dev_dbg(i2c->dev, "ack was not received\n"); s3c24xx_i2c_stop(i2c, -ENXIO); goto out_ack; @@ -441,9 +431,10 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) else i2c->state = STATE_WRITE; - /* terminate the transfer if there is nothing to do - * as this is used by the i2c probe to find devices. */ - + /* + * Terminate the transfer if there is nothing to do + * as this is used by the i2c probe to find devices. + */ if (is_lastmsg(i2c) && i2c->msg->len == 0) { s3c24xx_i2c_stop(i2c, 0); goto out_ack; @@ -452,14 +443,16 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) if (i2c->state == STATE_READ) goto prepare_read; - /* fall through to the write state, as we will need to - * send a byte as well */ + /* + * fall through to the write state, as we will need to + * send a byte as well + */ case STATE_WRITE: - /* we are writing data to the device... check for the + /* + * we are writing data to the device... check for the * end of the message, and if so, work out what to do */ - if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) { if (iicstat & S3C2410_IICSTAT_LASTBIT) { dev_dbg(i2c->dev, "WRITE: No Ack\n"); @@ -475,12 +468,13 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) byte = i2c->msg->buf[i2c->msg_ptr++]; writeb(byte, i2c->regs + S3C2410_IICDS); - /* delay after writing the byte to allow the + /* + * delay after writing the byte to allow the * data setup time on the bus, as writing the * data to the register causes the first bit * to appear on SDA, and SCL will change as - * soon as the interrupt is acknowledged */ - + * soon as the interrupt is acknowledged + */ ndelay(i2c->tx_setup); } else if (!is_lastmsg(i2c)) { @@ -496,10 +490,11 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) if (i2c->msg->flags & I2C_M_NOSTART) { if (i2c->msg->flags & I2C_M_RD) { - /* cannot do this, the controller + /* + * cannot do this, the controller * forces us to send a new START - * when we change direction */ - + * when we change direction + */ s3c24xx_i2c_stop(i2c, -EINVAL); } @@ -512,17 +507,16 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) } else { /* send stop */ - s3c24xx_i2c_stop(i2c, 0); } break; case STATE_READ: - /* we have a byte of data in the data register, do + /* + * we have a byte of data in the data register, do * something with it, and then work out whether we are * going to do any more read/write */ - byte = readb(i2c->regs + S3C2410_IICDS); i2c->msg->buf[i2c->msg_ptr++] = byte; @@ -537,9 +531,10 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) s3c24xx_i2c_disable_ack(i2c); } else if (is_msgend(i2c)) { - /* ok, we've read the entire buffer, see if there - * is anything else we need to do */ - + /* + * ok, we've read the entire buffer, see if there + * is anything else we need to do + */ if (is_lastmsg(i2c)) { /* last message, send stop and complete */ dev_dbg(i2c->dev, "READ: Send Stop\n"); @@ -568,11 +563,9 @@ static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) return ret; } -/* s3c24xx_i2c_irq - * +/* * top level IRQ servicing routine -*/ - + */ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) { struct s3c24xx_i2c *i2c = dev_id; @@ -595,9 +588,10 @@ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) goto out; } - /* pretty much this leaves us with the fact that we've - * transmitted or received whatever byte we last sent */ - + /* + * pretty much this leaves us with the fact that we've + * transmitted or received whatever byte we last sent + */ i2c_s3c_irq_nextbyte(i2c, status); out: @@ -630,11 +624,9 @@ static inline void s3c24xx_i2c_disable_bus(struct s3c24xx_i2c *i2c) } -/* s3c24xx_i2c_set_master - * +/* * get the i2c bus for a master transaction -*/ - + */ static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c) { unsigned long iicstat; @@ -652,11 +644,9 @@ static int s3c24xx_i2c_set_master(struct s3c24xx_i2c *i2c) return -ETIMEDOUT; } -/* s3c24xx_i2c_wait_idle - * +/* * wait for the i2c bus to become idle. -*/ - + */ static void s3c24xx_i2c_wait_idle(struct s3c24xx_i2c *i2c) { unsigned long iicstat; @@ -706,11 +696,9 @@ static void s3c24xx_i2c_wait_idle(struct s3c24xx_i2c *i2c) dev_warn(i2c->dev, "timeout waiting for bus idle\n"); } -/* s3c24xx_i2c_doxfer - * +/* * this starts an i2c transfer -*/ - + */ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, struct i2c_msg *msgs, int num) { @@ -749,9 +737,10 @@ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, ret = i2c->msg_idx; - /* having these next two as dev_err() makes life very - * noisy when doing an i2cdetect */ - + /* + * Having these next two as dev_err() makes life very + * noisy when doing an i2cdetect + */ if (timeout == 0) dev_dbg(i2c->dev, "timeout\n"); else if (ret != num) @@ -771,12 +760,10 @@ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c, return ret; } -/* s3c24xx_i2c_xfer - * +/* * first port of call from the i2c bus code when an message needs * transferring across the i2c bus. -*/ - + */ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { @@ -784,7 +771,6 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, int retry; int ret; - pm_runtime_get_sync(&adap->dev); ret = clk_enable(i2c->clk); if (ret) return ret; @@ -795,7 +781,6 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, if (ret != -EAGAIN) { clk_disable(i2c->clk); - pm_runtime_put(&adap->dev); return ret; } @@ -805,7 +790,6 @@ static int s3c24xx_i2c_xfer(struct i2c_adapter *adap, } clk_disable(i2c->clk); - pm_runtime_put(&adap->dev); return -EREMOTEIO; } @@ -817,17 +801,14 @@ static u32 s3c24xx_i2c_func(struct i2c_adapter *adap) } /* i2c bus registration info */ - static const struct i2c_algorithm s3c24xx_i2c_algorithm = { .master_xfer = s3c24xx_i2c_xfer, .functionality = s3c24xx_i2c_func, }; -/* s3c24xx_i2c_calcdivisor - * +/* * return the divisor settings for a given frequency -*/ - + */ static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted, unsigned int *div1, unsigned int *divs) { @@ -853,13 +834,11 @@ static int s3c24xx_i2c_calcdivisor(unsigned long clkin, unsigned int wanted, return clkin / (calc_divs * calc_div1); } -/* s3c24xx_i2c_clockrate - * +/* * work out a divisor for the user requested frequency setting, * either by the requested frequency, or scanning the acceptable * range of frequencies until something is found -*/ - + */ static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got) { struct s3c2410_platform_i2c *pdata = i2c->pdata; @@ -947,7 +926,7 @@ static int s3c24xx_i2c_cpufreq_transition(struct notifier_block *nb, i2c_unlock_adapter(&i2c->adap); if (ret < 0) - dev_err(i2c->dev, "cannot find frequency\n"); + dev_err(i2c->dev, "cannot find frequency (%d)\n", ret); else dev_info(i2c->dev, "setting freq %d\n", got); } @@ -998,7 +977,8 @@ static int s3c24xx_i2c_parse_dt_gpio(struct s3c24xx_i2c *i2c) ret = gpio_request(gpio, "i2c-bus"); if (ret) { - dev_err(i2c->dev, "gpio [%d] request failed\n", gpio); + dev_err(i2c->dev, "gpio [%d] request failed (%d)\n", + gpio, ret); goto free_gpio; } } @@ -1031,11 +1011,9 @@ static void s3c24xx_i2c_dt_gpio_free(struct s3c24xx_i2c *i2c) } #endif -/* s3c24xx_i2c_init - * +/* * initialise the controller, set the IO lines and frequency -*/ - + */ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c) { struct s3c2410_platform_i2c *pdata; @@ -1071,11 +1049,9 @@ static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c) } #ifdef CONFIG_OF -/* s3c24xx_i2c_parse_dt - * +/* * Parse the device tree node and retreive the platform data. -*/ - + */ static void s3c24xx_i2c_parse_dt(struct device_node *np, struct s3c24xx_i2c *i2c) { @@ -1108,17 +1084,9 @@ s3c24xx_i2c_parse_dt(struct device_node *np, struct s3c24xx_i2c *i2c) } #else static void -s3c24xx_i2c_parse_dt(struct device_node *np, struct s3c24xx_i2c *i2c) -{ - return; -} +s3c24xx_i2c_parse_dt(struct device_node *np, struct s3c24xx_i2c *i2c) { } #endif -/* s3c24xx_i2c_probe - * - * called by the bus driver when a suitable device is found -*/ - static int s3c24xx_i2c_probe(struct platform_device *pdev) { struct s3c24xx_i2c *i2c; @@ -1159,7 +1127,6 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) init_waitqueue_head(&i2c->wait); /* find the clock and enable it */ - i2c->dev = &pdev->dev; i2c->clk = devm_clk_get(&pdev->dev, "i2c"); if (IS_ERR(i2c->clk)) { @@ -1169,9 +1136,7 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk); - /* map the registers */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); i2c->regs = devm_ioremap_resource(&pdev->dev, res); @@ -1182,33 +1147,35 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) i2c->regs, res); /* setup info block for the i2c core */ - i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev; - i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev); /* inititalise the i2c gpio lines */ - - if (i2c->pdata->cfg_gpio) { + if (i2c->pdata->cfg_gpio) i2c->pdata->cfg_gpio(to_platform_device(i2c->dev)); - } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) { + else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) return -EINVAL; - } /* initialise the i2c controller */ + ret = clk_prepare_enable(i2c->clk); + if (ret) { + dev_err(&pdev->dev, "I2C clock enable failed\n"); + return ret; + } - clk_prepare_enable(i2c->clk); ret = s3c24xx_i2c_init(i2c); clk_disable(i2c->clk); if (ret != 0) { dev_err(&pdev->dev, "I2C controller init failed\n"); + clk_unprepare(i2c->clk); return ret; } - /* find the IRQ for this unit (note, this relies on the init call to + + /* + * find the IRQ for this unit (note, this relies on the init call to * ensure no current IRQs pending */ - if (!(i2c->quirks & QUIRK_POLL)) { i2c->irq = ret = platform_get_irq(pdev, 0); if (ret <= 0) { @@ -1217,9 +1184,8 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) return ret; } - ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0, - dev_name(&pdev->dev), i2c); - + ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, + 0, dev_name(&pdev->dev), i2c); if (ret != 0) { dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq); clk_unprepare(i2c->clk); @@ -1234,12 +1200,12 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) return ret; } - /* Note, previous versions of the driver used i2c_add_adapter() + /* + * Note, previous versions of the driver used i2c_add_adapter() * to add the bus at any number. We now pass the bus number via * the platform data, so if unset it will now default to always * being bus 0. */ - i2c->adap.nr = i2c->pdata->bus_num; i2c->adap.dev.of_node = pdev->dev.of_node; @@ -1256,24 +1222,16 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) return ret; } - pm_runtime_enable(&i2c->adap.dev); - dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); return 0; } -/* s3c24xx_i2c_remove - * - * called when device is removed from the bus -*/ - static int s3c24xx_i2c_remove(struct platform_device *pdev) { struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev); clk_unprepare(i2c->clk); - pm_runtime_disable(&i2c->adap.dev); pm_runtime_disable(&pdev->dev); s3c24xx_i2c_deregister_cpufreq(i2c); @@ -1322,14 +1280,8 @@ static int s3c24xx_i2c_resume_noirq(struct device *dev) #ifdef CONFIG_PM static const struct dev_pm_ops s3c24xx_i2c_dev_pm_ops = { -#ifdef CONFIG_PM_SLEEP - .suspend_noirq = s3c24xx_i2c_suspend_noirq, - .resume_noirq = s3c24xx_i2c_resume_noirq, - .freeze_noirq = s3c24xx_i2c_suspend_noirq, - .thaw_noirq = s3c24xx_i2c_resume_noirq, - .poweroff_noirq = s3c24xx_i2c_suspend_noirq, - .restore_noirq = s3c24xx_i2c_resume_noirq, -#endif + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(s3c24xx_i2c_suspend_noirq, + s3c24xx_i2c_resume_noirq) }; #define S3C24XX_DEV_PM_OPS (&s3c24xx_i2c_dev_pm_ops) @@ -1337,8 +1289,6 @@ static const struct dev_pm_ops s3c24xx_i2c_dev_pm_ops = { #define S3C24XX_DEV_PM_OPS NULL #endif -/* device driver for platform bus bits */ - static struct platform_driver s3c24xx_i2c_driver = { .probe = s3c24xx_i2c_probe, .remove = s3c24xx_i2c_remove, diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c index 7d2bd3e..6fb3e26 100644 --- a/drivers/i2c/busses/i2c-sh_mobile.c +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -398,8 +398,7 @@ static void sh_mobile_i2c_get_data(struct sh_mobile_i2c_data *pd, { switch (pd->pos) { case -1: - *buf = (pd->msg->addr & 0x7f) << 1; - *buf |= (pd->msg->flags & I2C_M_RD) ? 1 : 0; + *buf = i2c_8bit_addr_from_msg(pd->msg); break; default: *buf = pd->msg->buf[pd->pos]; diff --git a/drivers/i2c/busses/i2c-sirf.c b/drivers/i2c/busses/i2c-sirf.c index 13e51ef..792a42b 100644 --- a/drivers/i2c/busses/i2c-sirf.c +++ b/drivers/i2c/busses/i2c-sirf.c @@ -190,9 +190,7 @@ static void i2c_sirfsoc_set_address(struct sirfsoc_i2c *siic, writel(regval, siic->base + SIRFSOC_I2C_CMD(siic->cmd_ptr++)); - addr = msg->addr << 1; /* Generate address */ - if (msg->flags & I2C_M_RD) - addr |= 1; + addr = i2c_8bit_addr_from_msg(msg); /* Reverse direction bit */ if (msg->flags & I2C_M_REV_DIR_ADDR) diff --git a/drivers/i2c/busses/i2c-st.c b/drivers/i2c/busses/i2c-st.c index 25020ec..944ec42 100644 --- a/drivers/i2c/busses/i2c-st.c +++ b/drivers/i2c/busses/i2c-st.c @@ -337,10 +337,42 @@ static void st_i2c_hw_config(struct st_i2c_dev *i2c_dev) writel_relaxed(val, i2c_dev->base + SSC_NOISE_SUPP_WIDTH_DATAOUT); } +static int st_i2c_recover_bus(struct i2c_adapter *i2c_adap) +{ + struct st_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap); + u32 ctl; + + dev_dbg(i2c_dev->dev, "Trying to recover bus\n"); + + /* + * SSP IP is dual role SPI/I2C to generate 9 clock pulses + * we switch to SPI node, 9 bit words and write a 0. This + * has been validate with a oscilloscope and is easier + * than switching to GPIO mode. + */ + + /* Disable interrupts */ + writel_relaxed(0, i2c_dev->base + SSC_IEN); + + st_i2c_hw_config(i2c_dev); + + ctl = SSC_CTL_EN | SSC_CTL_MS | SSC_CTL_EN_RX_FIFO | SSC_CTL_EN_TX_FIFO; + st_i2c_set_bits(i2c_dev->base + SSC_CTL, ctl); + + st_i2c_clr_bits(i2c_dev->base + SSC_I2C, SSC_I2C_I2CM); + usleep_range(8000, 10000); + + writel_relaxed(0, i2c_dev->base + SSC_TBUF); + usleep_range(2000, 4000); + st_i2c_set_bits(i2c_dev->base + SSC_I2C, SSC_I2C_I2CM); + + return 0; +} + static int st_i2c_wait_free_bus(struct st_i2c_dev *i2c_dev) { u32 sta; - int i; + int i, ret; for (i = 0; i < 10; i++) { sta = readl_relaxed(i2c_dev->base + SSC_STA); @@ -352,6 +384,12 @@ static int st_i2c_wait_free_bus(struct st_i2c_dev *i2c_dev) dev_err(i2c_dev->dev, "bus not free (status = 0x%08x)\n", sta); + ret = i2c_recover_bus(&i2c_dev->adap); + if (ret) { + dev_err(i2c_dev->dev, "Failed to recover the bus (%d)\n", ret); + return ret; + } + return -EBUSY; } @@ -614,8 +652,7 @@ static int st_i2c_xfer_msg(struct st_i2c_dev *i2c_dev, struct i2c_msg *msg, unsigned long timeout; int ret; - c->addr = (u8)(msg->addr << 1); - c->addr |= (msg->flags & I2C_M_RD); + c->addr = i2c_8bit_addr_from_msg(msg); c->buf = msg->buf; c->count = msg->len; c->xfered = 0; @@ -708,8 +745,7 @@ static int st_i2c_xfer(struct i2c_adapter *i2c_adap, #ifdef CONFIG_PM_SLEEP static int st_i2c_suspend(struct device *dev) { - struct platform_device *pdev = - container_of(dev, struct platform_device, dev); + struct platform_device *pdev = to_platform_device(dev); struct st_i2c_dev *i2c_dev = platform_get_drvdata(pdev); if (i2c_dev->busy) @@ -745,6 +781,10 @@ static struct i2c_algorithm st_i2c_algo = { .functionality = st_i2c_func, }; +static struct i2c_bus_recovery_info st_i2c_recovery_info = { + .recover_bus = st_i2c_recover_bus, +}; + static int st_i2c_of_get_deglitch(struct device_node *np, struct st_i2c_dev *i2c_dev) { @@ -827,6 +867,7 @@ static int st_i2c_probe(struct platform_device *pdev) adap->timeout = 2 * HZ; adap->retries = 0; adap->algo = &st_i2c_algo; + adap->bus_recovery_info = &st_i2c_recovery_info; adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; diff --git a/drivers/i2c/busses/i2c-taos-evm.c b/drivers/i2c/busses/i2c-taos-evm.c index 4c7fc2d..210ca82 100644 --- a/drivers/i2c/busses/i2c-taos-evm.c +++ b/drivers/i2c/busses/i2c-taos-evm.c @@ -130,7 +130,13 @@ static int taos_smbus_xfer(struct i2c_adapter *adapter, u16 addr, return 0; } else { if (p[0] == 'x') { - data->byte = simple_strtol(p + 1, NULL, 16); + /* + * Voluntarily dropping error code of kstrtou8 since all + * error code that it could return are invalid according + * to Documentation/i2c/fault-codes. + */ + if (kstrtou8(p + 1, 16, &data->byte)) + return -EPROTO; return 0; } } diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c index a0522fc..b126dba 100644 --- a/drivers/i2c/busses/i2c-tegra.c +++ b/drivers/i2c/busses/i2c-tegra.c @@ -38,6 +38,7 @@ #define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12 #define I2C_CNFG_PACKET_MODE_EN (1<<10) #define I2C_CNFG_NEW_MASTER_FSM (1<<11) +#define I2C_CNFG_MULTI_MASTER_MODE (1<<17) #define I2C_STATUS 0x01C #define I2C_SL_CNFG 0x020 #define I2C_SL_CNFG_NACK (1<<1) @@ -106,6 +107,9 @@ #define I2C_SLV_CONFIG_LOAD (1 << 1) #define I2C_TIMEOUT_CONFIG_LOAD (1 << 2) +#define I2C_CLKEN_OVERRIDE 0x090 +#define I2C_MST_CORE_CLKEN_OVR (1 << 0) + /* * msg_end_type: The bus control which need to be send at end of transfer. * @MSG_END_STOP: Send stop pulse at end of transfer. @@ -143,6 +147,8 @@ struct tegra_i2c_hw_feature { int clk_divisor_hs_mode; int clk_divisor_std_fast_mode; u16 clk_divisor_fast_plus_mode; + bool has_multi_master_mode; + bool has_slcg_override_reg; }; /** @@ -184,6 +190,7 @@ struct tegra_i2c_dev { u32 bus_clk_rate; u16 clk_divisor_non_hs_mode; bool is_suspended; + bool is_multimaster_mode; }; static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg) @@ -438,6 +445,10 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) val = I2C_CNFG_NEW_MASTER_FSM | I2C_CNFG_PACKET_MODE_EN | (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); + + if (i2c_dev->hw->has_multi_master_mode) + val |= I2C_CNFG_MULTI_MASTER_MODE; + i2c_writel(i2c_dev, val, I2C_CNFG); i2c_writel(i2c_dev, 0, I2C_INT_MASK); @@ -463,25 +474,29 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) if (tegra_i2c_flush_fifos(i2c_dev)) err = -ETIMEDOUT; + if (i2c_dev->is_multimaster_mode && i2c_dev->hw->has_slcg_override_reg) + i2c_writel(i2c_dev, I2C_MST_CORE_CLKEN_OVR, I2C_CLKEN_OVERRIDE); + if (i2c_dev->hw->has_config_load_reg) { i2c_writel(i2c_dev, I2C_MSTR_CONFIG_LOAD, I2C_CONFIG_LOAD); while (i2c_readl(i2c_dev, I2C_CONFIG_LOAD) != 0) { if (time_after(jiffies, timeout)) { dev_warn(i2c_dev->dev, "timeout waiting for config load\n"); - return -ETIMEDOUT; + err = -ETIMEDOUT; + goto err; } msleep(1); } } - tegra_i2c_clock_disable(i2c_dev); - if (i2c_dev->irq_disabled) { i2c_dev->irq_disabled = 0; enable_irq(i2c_dev->irq); } +err: + tegra_i2c_clock_disable(i2c_dev); return err; } @@ -688,6 +703,20 @@ static u32 tegra_i2c_func(struct i2c_adapter *adap) return ret; } +static void tegra_i2c_parse_dt(struct tegra_i2c_dev *i2c_dev) +{ + struct device_node *np = i2c_dev->dev->of_node; + int ret; + + ret = of_property_read_u32(np, "clock-frequency", + &i2c_dev->bus_clk_rate); + if (ret) + i2c_dev->bus_clk_rate = 100000; /* default clock rate */ + + i2c_dev->is_multimaster_mode = of_property_read_bool(np, + "multi-master"); +} + static const struct i2c_algorithm tegra_i2c_algo = { .master_xfer = tegra_i2c_xfer, .functionality = tegra_i2c_func, @@ -707,6 +736,8 @@ static const struct tegra_i2c_hw_feature tegra20_i2c_hw = { .clk_divisor_std_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { @@ -717,6 +748,8 @@ static const struct tegra_i2c_hw_feature tegra30_i2c_hw = { .clk_divisor_std_fast_mode = 0, .clk_divisor_fast_plus_mode = 0, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { @@ -727,6 +760,8 @@ static const struct tegra_i2c_hw_feature tegra114_i2c_hw = { .clk_divisor_std_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = false, + .has_multi_master_mode = false, + .has_slcg_override_reg = false, }; static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { @@ -737,10 +772,25 @@ static const struct tegra_i2c_hw_feature tegra124_i2c_hw = { .clk_divisor_std_fast_mode = 0x19, .clk_divisor_fast_plus_mode = 0x10, .has_config_load_reg = true, + .has_multi_master_mode = false, + .has_slcg_override_reg = true, +}; + +static const struct tegra_i2c_hw_feature tegra210_i2c_hw = { + .has_continue_xfer_support = true, + .has_per_pkt_xfer_complete_irq = true, + .has_single_clk_source = true, + .clk_divisor_hs_mode = 1, + .clk_divisor_std_fast_mode = 0x19, + .clk_divisor_fast_plus_mode = 0x10, + .has_config_load_reg = true, + .has_multi_master_mode = true, + .has_slcg_override_reg = true, }; /* Match table for of_platform binding */ static const struct of_device_id tegra_i2c_of_match[] = { + { .compatible = "nvidia,tegra210-i2c", .data = &tegra210_i2c_hw, }, { .compatible = "nvidia,tegra124-i2c", .data = &tegra124_i2c_hw, }, { .compatible = "nvidia,tegra114-i2c", .data = &tegra114_i2c_hw, }, { .compatible = "nvidia,tegra30-i2c", .data = &tegra30_i2c_hw, }, @@ -797,17 +847,12 @@ static int tegra_i2c_probe(struct platform_device *pdev) return PTR_ERR(i2c_dev->rst); } - ret = of_property_read_u32(i2c_dev->dev->of_node, "clock-frequency", - &i2c_dev->bus_clk_rate); - if (ret) - i2c_dev->bus_clk_rate = 100000; /* default clock rate */ + tegra_i2c_parse_dt(i2c_dev); i2c_dev->hw = &tegra20_i2c_hw; if (pdev->dev.of_node) { - const struct of_device_id *match; - match = of_match_device(tegra_i2c_of_match, &pdev->dev); - i2c_dev->hw = match->data; + i2c_dev->hw = of_device_get_match_data(&pdev->dev); i2c_dev->is_dvc = of_device_is_compatible(pdev->dev.of_node, "nvidia,tegra20-i2c-dvc"); } else if (pdev->id == 3) { @@ -855,17 +900,26 @@ static int tegra_i2c_probe(struct platform_device *pdev) goto unprepare_fast_clk; } + if (i2c_dev->is_multimaster_mode) { + ret = clk_enable(i2c_dev->div_clk); + if (ret < 0) { + dev_err(i2c_dev->dev, "div_clk enable failed %d\n", + ret); + goto unprepare_div_clk; + } + } + ret = tegra_i2c_init(i2c_dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize i2c controller"); - goto unprepare_div_clk; + goto disable_div_clk; } ret = devm_request_irq(&pdev->dev, i2c_dev->irq, tegra_i2c_isr, 0, dev_name(&pdev->dev), i2c_dev); if (ret) { dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq); - goto unprepare_div_clk; + goto disable_div_clk; } i2c_set_adapdata(&i2c_dev->adapter, i2c_dev); @@ -880,11 +934,15 @@ static int tegra_i2c_probe(struct platform_device *pdev) ret = i2c_add_numbered_adapter(&i2c_dev->adapter); if (ret) { dev_err(&pdev->dev, "Failed to add I2C adapter\n"); - goto unprepare_div_clk; + goto disable_div_clk; } return 0; +disable_div_clk: + if (i2c_dev->is_multimaster_mode) + clk_disable(i2c_dev->div_clk); + unprepare_div_clk: clk_unprepare(i2c_dev->div_clk); @@ -900,6 +958,9 @@ static int tegra_i2c_remove(struct platform_device *pdev) struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev); i2c_del_adapter(&i2c_dev->adapter); + if (i2c_dev->is_multimaster_mode) + clk_disable(i2c_dev->div_clk); + clk_unprepare(i2c_dev->div_clk); if (!i2c_dev->hw->has_single_clk_source) clk_unprepare(i2c_dev->fast_clk); diff --git a/drivers/i2c/busses/i2c-uniphier-f.c b/drivers/i2c/busses/i2c-uniphier-f.c index e8d03bc..aeead0d 100644 --- a/drivers/i2c/busses/i2c-uniphier-f.c +++ b/drivers/i2c/busses/i2c-uniphier-f.c @@ -466,6 +466,11 @@ static int uniphier_fi2c_clk_init(struct device *dev, if (of_property_read_u32(np, "clock-frequency", &bus_speed)) bus_speed = UNIPHIER_FI2C_DEFAULT_SPEED; + if (!bus_speed) { + dev_err(dev, "clock-frequency should not be zero\n"); + return -EINVAL; + } + if (bus_speed > UNIPHIER_FI2C_MAX_SPEED) bus_speed = UNIPHIER_FI2C_MAX_SPEED; @@ -481,6 +486,10 @@ static int uniphier_fi2c_clk_init(struct device *dev, return ret; clk_rate = clk_get_rate(priv->clk); + if (!clk_rate) { + dev_err(dev, "input clock rate should not be zero\n"); + return -EINVAL; + } uniphier_fi2c_reset(priv); @@ -515,7 +524,7 @@ static int uniphier_fi2c_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(dev, "failed to get IRQ number"); + dev_err(dev, "failed to get IRQ number\n"); return irq; } @@ -531,7 +540,7 @@ static int uniphier_fi2c_probe(struct platform_device *pdev) ret = uniphier_fi2c_clk_init(dev, priv); if (ret) - return ret; + goto err; ret = devm_request_irq(dev, irq, uniphier_fi2c_interrupt, 0, pdev->name, priv); diff --git a/drivers/i2c/busses/i2c-uniphier.c b/drivers/i2c/busses/i2c-uniphier.c index e3c3861..475a5eb 100644 --- a/drivers/i2c/busses/i2c-uniphier.c +++ b/drivers/i2c/busses/i2c-uniphier.c @@ -327,6 +327,11 @@ static int uniphier_i2c_clk_init(struct device *dev, if (of_property_read_u32(np, "clock-frequency", &bus_speed)) bus_speed = UNIPHIER_I2C_DEFAULT_SPEED; + if (!bus_speed) { + dev_err(dev, "clock-frequency should not be zero\n"); + return -EINVAL; + } + if (bus_speed > UNIPHIER_I2C_MAX_SPEED) bus_speed = UNIPHIER_I2C_MAX_SPEED; @@ -342,6 +347,10 @@ static int uniphier_i2c_clk_init(struct device *dev, return ret; clk_rate = clk_get_rate(priv->clk); + if (!clk_rate) { + dev_err(dev, "input clock rate should not be zero\n"); + return -EINVAL; + } uniphier_i2c_reset(priv, true); @@ -372,7 +381,7 @@ static int uniphier_i2c_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(dev, "failed to get IRQ number"); + dev_err(dev, "failed to get IRQ number\n"); return irq; } @@ -388,7 +397,7 @@ static int uniphier_i2c_probe(struct platform_device *pdev) ret = uniphier_i2c_clk_init(dev, priv); if (ret) - return ret; + goto err; ret = devm_request_irq(dev, irq, uniphier_i2c_interrupt, 0, pdev->name, priv); diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c index 0b20449..74f54f2 100644 --- a/drivers/i2c/busses/i2c-xiic.c +++ b/drivers/i2c/busses/i2c-xiic.c @@ -37,6 +37,8 @@ #include #include #include +#include +#include #define DRIVER_NAME "xiic-i2c" @@ -66,17 +68,19 @@ enum xiic_endian { * @endianness: big/little-endian byte order */ struct xiic_i2c { + struct device *dev; void __iomem *base; wait_queue_head_t wait; struct i2c_adapter adap; struct i2c_msg *tx_msg; - spinlock_t lock; + struct mutex lock; unsigned int tx_pos; unsigned int nmsgs; enum xilinx_i2c_state state; struct i2c_msg *rx_msg; int rx_pos; enum xiic_endian endianness; + struct clk *clk; }; @@ -164,6 +168,7 @@ struct xiic_i2c { #define XIIC_RESET_MASK 0xAUL +#define XIIC_PM_TIMEOUT 1000 /* ms */ /* * The following constant is used for the device global interrupt enable * register, to enable all interrupts for the device, this is the only bit @@ -369,7 +374,7 @@ static irqreturn_t xiic_process(int irq, void *dev_id) * To find which interrupts are pending; AND interrupts pending with * interrupts masked. */ - spin_lock(&i2c->lock); + mutex_lock(&i2c->lock); isr = xiic_getreg32(i2c, XIIC_IISR_OFFSET); ier = xiic_getreg32(i2c, XIIC_IIER_OFFSET); pend = isr & ier; @@ -497,7 +502,7 @@ out: dev_dbg(i2c->adap.dev.parent, "%s clr: 0x%x\n", __func__, clr); xiic_setreg32(i2c, XIIC_IISR_OFFSET, clr); - spin_unlock(&i2c->lock); + mutex_unlock(&i2c->lock); return IRQ_HANDLED; } @@ -662,10 +667,10 @@ static void __xiic_start_xfer(struct xiic_i2c *i2c) static void xiic_start_xfer(struct xiic_i2c *i2c) { - spin_lock(&i2c->lock); + mutex_lock(&i2c->lock); xiic_reinit(i2c); __xiic_start_xfer(i2c); - spin_unlock(&i2c->lock); + mutex_unlock(&i2c->lock); } static int xiic_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) @@ -676,9 +681,13 @@ static int xiic_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) dev_dbg(adap->dev.parent, "%s entry SR: 0x%x\n", __func__, xiic_getreg8(i2c, XIIC_SR_REG_OFFSET)); + err = pm_runtime_get_sync(i2c->dev); + if (err < 0) + return err; + err = xiic_busy(i2c); if (err) - return err; + goto out; i2c->tx_msg = msgs; i2c->nmsgs = num; @@ -686,14 +695,20 @@ static int xiic_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) xiic_start_xfer(i2c); if (wait_event_timeout(i2c->wait, (i2c->state == STATE_ERROR) || - (i2c->state == STATE_DONE), HZ)) - return (i2c->state == STATE_DONE) ? num : -EIO; - else { + (i2c->state == STATE_DONE), HZ)) { + err = (i2c->state == STATE_DONE) ? num : -EIO; + goto out; + } else { i2c->tx_msg = NULL; i2c->rx_msg = NULL; i2c->nmsgs = 0; - return -ETIMEDOUT; + err = -ETIMEDOUT; + goto out; } +out: + pm_runtime_mark_last_busy(i2c->dev); + pm_runtime_put_autosuspend(i2c->dev); + return err; } static u32 xiic_func(struct i2c_adapter *adap) @@ -745,16 +760,31 @@ static int xiic_i2c_probe(struct platform_device *pdev) i2c->adap.dev.parent = &pdev->dev; i2c->adap.dev.of_node = pdev->dev.of_node; - spin_lock_init(&i2c->lock); + mutex_init(&i2c->lock); init_waitqueue_head(&i2c->wait); + i2c->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(i2c->clk)) { + dev_err(&pdev->dev, "input clock not found.\n"); + return PTR_ERR(i2c->clk); + } + ret = clk_prepare_enable(i2c->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock.\n"); + return ret; + } + i2c->dev = &pdev->dev; + pm_runtime_enable(i2c->dev); + pm_runtime_set_autosuspend_delay(i2c->dev, XIIC_PM_TIMEOUT); + pm_runtime_use_autosuspend(i2c->dev); + pm_runtime_set_active(i2c->dev); ret = devm_request_threaded_irq(&pdev->dev, irq, xiic_isr, xiic_process, IRQF_ONESHOT, pdev->name, i2c); if (ret < 0) { dev_err(&pdev->dev, "Cannot claim IRQ\n"); - return ret; + goto err_clk_dis; } /* @@ -776,7 +806,7 @@ static int xiic_i2c_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "Failed to add adapter\n"); xiic_deinit(i2c); - return ret; + goto err_clk_dis; } if (pdata) { @@ -786,16 +816,30 @@ static int xiic_i2c_probe(struct platform_device *pdev) } return 0; + +err_clk_dis: + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_disable(&pdev->dev); + clk_disable_unprepare(i2c->clk); + return ret; } static int xiic_i2c_remove(struct platform_device *pdev) { struct xiic_i2c *i2c = platform_get_drvdata(pdev); + int ret; /* remove adapter & data */ i2c_del_adapter(&i2c->adap); + ret = clk_prepare_enable(i2c->clk); + if (ret) { + dev_err(&pdev->dev, "Unable to enable clock.\n"); + return ret; + } xiic_deinit(i2c); + clk_disable_unprepare(i2c->clk); + pm_runtime_disable(&pdev->dev); return 0; } @@ -808,12 +852,42 @@ static const struct of_device_id xiic_of_match[] = { MODULE_DEVICE_TABLE(of, xiic_of_match); #endif +static int __maybe_unused cdns_i2c_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xiic_i2c *i2c = platform_get_drvdata(pdev); + + clk_disable(i2c->clk); + + return 0; +} + +static int __maybe_unused cdns_i2c_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct xiic_i2c *i2c = platform_get_drvdata(pdev); + int ret; + + ret = clk_enable(i2c->clk); + if (ret) { + dev_err(dev, "Cannot enable clock.\n"); + return ret; + } + + return 0; +} + +static const struct dev_pm_ops xiic_dev_pm_ops = { + SET_RUNTIME_PM_OPS(cdns_i2c_runtime_suspend, + cdns_i2c_runtime_resume, NULL) +}; static struct platform_driver xiic_i2c_driver = { .probe = xiic_i2c_probe, .remove = xiic_i2c_remove, .driver = { .name = DRIVER_NAME, .of_match_table = of_match_ptr(xiic_of_match), + .pm = &xiic_dev_pm_ops, }, }; diff --git a/drivers/i2c/busses/i2c-xlr.c b/drivers/i2c/busses/i2c-xlr.c index 8b36bcf..613c3a4 100644 --- a/drivers/i2c/busses/i2c-xlr.c +++ b/drivers/i2c/busses/i2c-xlr.c @@ -17,6 +17,10 @@ #include #include #include +#include +#include +#include +#include /* XLR I2C REGISTERS */ #define XLR_I2C_CFG 0x00 @@ -30,6 +34,10 @@ #define XLR_I2C_BYTECNT 0x08 #define XLR_I2C_HDSTATIM 0x09 +/* Sigma Designs additional registers */ +#define XLR_I2C_INT_EN 0x09 +#define XLR_I2C_INT_STAT 0x0a + /* XLR I2C REGISTERS FLAGS */ #define XLR_I2C_BUS_BUSY 0x01 #define XLR_I2C_SDOEMPTY 0x02 @@ -63,11 +71,98 @@ static inline u32 xlr_i2c_rdreg(u32 __iomem *base, unsigned int reg) return __raw_readl(base + reg); } +#define XLR_I2C_FLAG_IRQ 1 + +struct xlr_i2c_config { + u32 flags; /* optional feature support */ + u32 status_busy; /* value of STATUS[0] when busy */ + u32 cfg_extra; /* extra CFG bits to set */ +}; + struct xlr_i2c_private { struct i2c_adapter adap; u32 __iomem *iobase; + int irq; + int pos; + struct i2c_msg *msg; + const struct xlr_i2c_config *cfg; + wait_queue_head_t wait; + struct clk *clk; }; +static int xlr_i2c_busy(struct xlr_i2c_private *priv, u32 status) +{ + return (status & XLR_I2C_BUS_BUSY) == priv->cfg->status_busy; +} + +static int xlr_i2c_idle(struct xlr_i2c_private *priv) +{ + return !xlr_i2c_busy(priv, xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS)); +} + +static int xlr_i2c_wait(struct xlr_i2c_private *priv, unsigned long timeout) +{ + int status; + int t; + + t = wait_event_timeout(priv->wait, xlr_i2c_idle(priv), + msecs_to_jiffies(timeout)); + if (!t) + return -ETIMEDOUT; + + status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); + + return status & XLR_I2C_ACK_ERR ? -EIO : 0; +} + +static void xlr_i2c_tx_irq(struct xlr_i2c_private *priv, u32 status) +{ + struct i2c_msg *msg = priv->msg; + + if (status & XLR_I2C_SDOEMPTY) + xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, + msg->buf[priv->pos++]); +} + +static void xlr_i2c_rx_irq(struct xlr_i2c_private *priv, u32 status) +{ + struct i2c_msg *msg = priv->msg; + + if (status & XLR_I2C_RXRDY) + msg->buf[priv->pos++] = + xlr_i2c_rdreg(priv->iobase, XLR_I2C_DATAIN); +} + +static irqreturn_t xlr_i2c_irq(int irq, void *dev_id) +{ + struct xlr_i2c_private *priv = dev_id; + struct i2c_msg *msg = priv->msg; + u32 int_stat, status; + + int_stat = xlr_i2c_rdreg(priv->iobase, XLR_I2C_INT_STAT); + if (!int_stat) + return IRQ_NONE; + + xlr_i2c_wreg(priv->iobase, XLR_I2C_INT_STAT, int_stat); + + if (!msg) + return IRQ_HANDLED; + + status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); + + if (priv->pos < msg->len) { + if (msg->flags & I2C_M_RD) + xlr_i2c_rx_irq(priv, status); + else + xlr_i2c_tx_irq(priv, status); + } + + if (!xlr_i2c_busy(priv, status)) + wake_up(&priv->wait); + + return IRQ_HANDLED; +} + static int xlr_i2c_tx(struct xlr_i2c_private *priv, u16 len, u8 *buf, u16 addr) { @@ -75,37 +170,48 @@ static int xlr_i2c_tx(struct xlr_i2c_private *priv, u16 len, unsigned long timeout, stoptime, checktime; u32 i2c_status; int pos, timedout; - u8 offset, byte; + u8 offset; + u32 xfer; + + if (!len) + return -EOPNOTSUPP; offset = buf[0]; xlr_i2c_wreg(priv->iobase, XLR_I2C_ADDR, offset); xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); - xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_ADDR); - xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 1); + xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, + XLR_I2C_CFG_ADDR | priv->cfg->cfg_extra); timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); stoptime = jiffies + timeout; timedout = 0; - pos = 1; -retry: + if (len == 1) { - xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, - XLR_I2C_STARTXFR_ND); + xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 1); + xfer = XLR_I2C_STARTXFR_ND; + pos = 1; } else { - xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, buf[pos]); - xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, - XLR_I2C_STARTXFR_WR); + xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 2); + xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, buf[1]); + xfer = XLR_I2C_STARTXFR_WR; + pos = 2; } + priv->pos = pos; + +retry: + /* retry can only happen on the first byte */ + xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, xfer); + + if (priv->irq > 0) + return xlr_i2c_wait(priv, XLR_I2C_TIMEOUT * len); + while (!timedout) { checktime = jiffies; i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); - if (i2c_status & XLR_I2C_SDOEMPTY) { - pos++; - /* need to do a empty dataout after the last byte */ - byte = (pos < len) ? buf[pos] : 0; - xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, byte); + if ((i2c_status & XLR_I2C_SDOEMPTY) && pos < len) { + xlr_i2c_wreg(priv->iobase, XLR_I2C_DATAOUT, buf[pos++]); /* reset timeout on successful xmit */ stoptime = jiffies + timeout; @@ -121,7 +227,7 @@ retry: if (i2c_status & XLR_I2C_ACK_ERR) return -EIO; - if ((i2c_status & XLR_I2C_BUS_BUSY) == 0 && pos >= len) + if (!xlr_i2c_busy(priv, i2c_status) && pos >= len) return 0; } dev_err(&adap->dev, "I2C transmit timeout\n"); @@ -134,12 +240,17 @@ static int xlr_i2c_rx(struct xlr_i2c_private *priv, u16 len, u8 *buf, u16 addr) u32 i2c_status; unsigned long timeout, stoptime, checktime; int nbytes, timedout; - u8 byte; - xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, XLR_I2C_CFG_NOADDR); - xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len); + if (!len) + return -EOPNOTSUPP; + + xlr_i2c_wreg(priv->iobase, XLR_I2C_CFG, + XLR_I2C_CFG_NOADDR | priv->cfg->cfg_extra); + xlr_i2c_wreg(priv->iobase, XLR_I2C_BYTECNT, len - 1); xlr_i2c_wreg(priv->iobase, XLR_I2C_DEVADDR, addr); + priv->pos = 0; + timeout = msecs_to_jiffies(XLR_I2C_TIMEOUT); stoptime = jiffies + timeout; timedout = 0; @@ -147,18 +258,18 @@ static int xlr_i2c_rx(struct xlr_i2c_private *priv, u16 len, u8 *buf, u16 addr) retry: xlr_i2c_wreg(priv->iobase, XLR_I2C_STARTXFR, XLR_I2C_STARTXFR_RD); + if (priv->irq > 0) + return xlr_i2c_wait(priv, XLR_I2C_TIMEOUT * len); + while (!timedout) { checktime = jiffies; i2c_status = xlr_i2c_rdreg(priv->iobase, XLR_I2C_STATUS); if (i2c_status & XLR_I2C_RXRDY) { - if (nbytes > len) + if (nbytes >= len) return -EIO; /* should not happen */ - /* we need to do a dummy datain when nbytes == len */ - byte = xlr_i2c_rdreg(priv->iobase, XLR_I2C_DATAIN); - if (nbytes < len) - buf[nbytes] = byte; - nbytes++; + buf[nbytes++] = + xlr_i2c_rdreg(priv->iobase, XLR_I2C_DATAIN); /* reset timeout on successful read */ stoptime = jiffies + timeout; @@ -174,7 +285,7 @@ retry: if (i2c_status & XLR_I2C_ACK_ERR) return -EIO; - if ((i2c_status & XLR_I2C_BUS_BUSY) == 0) + if (!xlr_i2c_busy(priv, i2c_status)) return 0; } @@ -190,8 +301,17 @@ static int xlr_i2c_xfer(struct i2c_adapter *adap, int ret = 0; struct xlr_i2c_private *priv = i2c_get_adapdata(adap); + ret = clk_enable(priv->clk); + if (ret) + return ret; + + if (priv->irq) + xlr_i2c_wreg(priv->iobase, XLR_I2C_INT_EN, 0xf); + + for (i = 0; ret == 0 && i < num; i++) { msg = &msgs[i]; + priv->msg = msg; if (msg->flags & I2C_M_RD) ret = xlr_i2c_rx(priv, msg->len, &msg->buf[0], msg->addr); @@ -200,13 +320,19 @@ static int xlr_i2c_xfer(struct i2c_adapter *adap, msg->addr); } + if (priv->irq) + xlr_i2c_wreg(priv->iobase, XLR_I2C_INT_EN, 0); + + clk_disable(priv->clk); + priv->msg = NULL; + return (ret != 0) ? ret : num; } static u32 xlr_func(struct i2c_adapter *adap) { /* Emulate SMBUS over I2C */ - return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C; + return (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) | I2C_FUNC_I2C; } static struct i2c_algorithm xlr_i2c_algo = { @@ -214,22 +340,89 @@ static struct i2c_algorithm xlr_i2c_algo = { .functionality = xlr_func, }; +static const struct xlr_i2c_config xlr_i2c_config_default = { + .status_busy = XLR_I2C_BUS_BUSY, + .cfg_extra = 0, +}; + +static const struct xlr_i2c_config xlr_i2c_config_tangox = { + .flags = XLR_I2C_FLAG_IRQ, + .status_busy = 0, + .cfg_extra = 1 << 8, +}; + +static const struct of_device_id xlr_i2c_dt_ids[] = { + { + .compatible = "sigma,smp8642-i2c", + .data = &xlr_i2c_config_tangox, + }, + { } +}; + static int xlr_i2c_probe(struct platform_device *pdev) { + const struct of_device_id *match; struct xlr_i2c_private *priv; struct resource *res; + struct clk *clk; + unsigned long clk_rate; + unsigned long clk_div; + u32 busfreq; + int irq; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; + match = of_match_device(xlr_i2c_dt_ids, &pdev->dev); + if (match) + priv->cfg = match->data; + else + priv->cfg = &xlr_i2c_config_default; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->iobase = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(priv->iobase)) return PTR_ERR(priv->iobase); + irq = platform_get_irq(pdev, 0); + + if (irq > 0 && (priv->cfg->flags & XLR_I2C_FLAG_IRQ)) { + priv->irq = irq; + + xlr_i2c_wreg(priv->iobase, XLR_I2C_INT_EN, 0); + xlr_i2c_wreg(priv->iobase, XLR_I2C_INT_STAT, 0xf); + + ret = devm_request_irq(&pdev->dev, priv->irq, xlr_i2c_irq, + IRQF_SHARED, dev_name(&pdev->dev), + priv); + if (ret) + return ret; + + init_waitqueue_head(&priv->wait); + } + + if (of_property_read_u32(pdev->dev.of_node, "clock-frequency", + &busfreq)) + busfreq = 100000; + + clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) { + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + clk_rate = clk_get_rate(clk); + clk_div = DIV_ROUND_UP(clk_rate, 2 * busfreq); + xlr_i2c_wreg(priv->iobase, XLR_I2C_CLKDIV, clk_div); + + clk_disable(clk); + priv->clk = clk; + } + priv->adap.dev.parent = &pdev->dev; + priv->adap.dev.of_node = pdev->dev.of_node; priv->adap.owner = THIS_MODULE; priv->adap.algo_data = priv; priv->adap.algo = &xlr_i2c_algo; @@ -255,6 +448,8 @@ static int xlr_i2c_remove(struct platform_device *pdev) priv = platform_get_drvdata(pdev); i2c_del_adapter(&priv->adap); + clk_unprepare(priv->clk); + return 0; } @@ -263,6 +458,7 @@ static struct platform_driver xlr_i2c_driver = { .remove = xlr_i2c_remove, .driver = { .name = "xlr-i2cbus", + .of_match_table = xlr_i2c_dt_ids, }, }; diff --git a/drivers/i2c/i2c-boardinfo.c b/drivers/i2c/i2c-boardinfo.c index 90e3229..6e5fac6 100644 --- a/drivers/i2c/i2c-boardinfo.c +++ b/drivers/i2c/i2c-boardinfo.c @@ -12,11 +12,11 @@ * GNU General Public License for more details. */ -#include -#include -#include #include +#include +#include #include +#include #include "i2c-core.h" @@ -56,9 +56,7 @@ EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num); * The board info passed can safely be __initdata, but be careful of embedded * pointers (for platform_data, functions, etc) since that won't be copied. */ -int __init -i2c_register_board_info(int busnum, - struct i2c_board_info const *info, unsigned len) +int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len) { int status; diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index ba8eb08..af11b65 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -28,31 +28,32 @@ */ #include -#include -#include +#include +#include +#include +#include #include +#include #include #include -#include +#include #include -#include #include +#include +#include +#include +#include +#include #include -#include #include +#include #include -#include -#include -#include -#include -#include -#include #include +#include #include -#include -#include -#include -#include +#include +#include +#include #include "i2c-core.h" @@ -72,6 +73,7 @@ static struct device_type i2c_client_type; static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver); static struct static_key i2c_trace_msg = STATIC_KEY_INIT_FALSE; +static bool is_registered; void i2c_transfer_trace_reg(void) { @@ -523,22 +525,16 @@ static int i2c_device_match(struct device *dev, struct device_driver *drv) return 0; } - -/* uevent helps with hotplug: modprobe -q $(MODALIAS) */ static int i2c_device_uevent(struct device *dev, struct kobj_uevent_env *env) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = to_i2c_client(dev); int rc; rc = acpi_device_uevent_modalias(dev, env); if (rc != -ENODEV) return rc; - if (add_uevent_var(env, "MODALIAS=%s%s", - I2C_MODULE_PREFIX, client->name)) - return -ENOMEM; - dev_dbg(dev, "uevent\n"); - return 0; + return add_uevent_var(env, "MODALIAS=%s%s", I2C_MODULE_PREFIX, client->name); } /* i2c bus recovery routines */ @@ -958,48 +954,40 @@ static int i2c_check_addr_busy(struct i2c_adapter *adapter, int addr) } /** - * i2c_lock_adapter - Get exclusive access to an I2C bus segment + * i2c_adapter_lock_bus - Get exclusive access to an I2C bus segment * @adapter: Target I2C bus segment + * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT + * locks only this branch in the adapter tree */ -void i2c_lock_adapter(struct i2c_adapter *adapter) +static void i2c_adapter_lock_bus(struct i2c_adapter *adapter, + unsigned int flags) { - struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter); - - if (parent) - i2c_lock_adapter(parent); - else - rt_mutex_lock(&adapter->bus_lock); + rt_mutex_lock(&adapter->bus_lock); } -EXPORT_SYMBOL_GPL(i2c_lock_adapter); /** - * i2c_trylock_adapter - Try to get exclusive access to an I2C bus segment + * i2c_adapter_trylock_bus - Try to get exclusive access to an I2C bus segment * @adapter: Target I2C bus segment + * @flags: I2C_LOCK_ROOT_ADAPTER trylocks the root i2c adapter, I2C_LOCK_SEGMENT + * trylocks only this branch in the adapter tree */ -static int i2c_trylock_adapter(struct i2c_adapter *adapter) +static int i2c_adapter_trylock_bus(struct i2c_adapter *adapter, + unsigned int flags) { - struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter); - - if (parent) - return i2c_trylock_adapter(parent); - else - return rt_mutex_trylock(&adapter->bus_lock); + return rt_mutex_trylock(&adapter->bus_lock); } /** - * i2c_unlock_adapter - Release exclusive access to an I2C bus segment + * i2c_adapter_unlock_bus - Release exclusive access to an I2C bus segment * @adapter: Target I2C bus segment + * @flags: I2C_LOCK_ROOT_ADAPTER unlocks the root i2c adapter, I2C_LOCK_SEGMENT + * unlocks only this branch in the adapter tree */ -void i2c_unlock_adapter(struct i2c_adapter *adapter) +static void i2c_adapter_unlock_bus(struct i2c_adapter *adapter, + unsigned int flags) { - struct i2c_adapter *parent = i2c_parent_is_i2c_adapter(adapter); - - if (parent) - i2c_unlock_adapter(parent); - else - rt_mutex_unlock(&adapter->bus_lock); + rt_mutex_unlock(&adapter->bus_lock); } -EXPORT_SYMBOL_GPL(i2c_unlock_adapter); static void i2c_dev_set_name(struct i2c_adapter *adap, struct i2c_client *client) @@ -1528,7 +1516,7 @@ static int i2c_register_adapter(struct i2c_adapter *adap) int res = 0; /* Can't register until after driver model init */ - if (unlikely(WARN_ON(!i2c_bus_type.p))) { + if (WARN_ON(!is_registered)) { res = -EAGAIN; goto out_list; } @@ -1545,7 +1533,14 @@ static int i2c_register_adapter(struct i2c_adapter *adap) return -EINVAL; } + if (!adap->lock_bus) { + adap->lock_bus = i2c_adapter_lock_bus; + adap->trylock_bus = i2c_adapter_trylock_bus; + adap->unlock_bus = i2c_adapter_unlock_bus; + } + rt_mutex_init(&adap->bus_lock); + rt_mutex_init(&adap->mux_lock); mutex_init(&adap->userspace_clients_lock); INIT_LIST_HEAD(&adap->userspace_clients); @@ -1563,6 +1558,8 @@ static int i2c_register_adapter(struct i2c_adapter *adap) dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); pm_runtime_no_callbacks(&adap->dev); + pm_suspend_ignore_children(&adap->dev, true); + pm_runtime_enable(&adap->dev); #ifdef CONFIG_I2C_COMPAT res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev, @@ -1597,10 +1594,12 @@ static int i2c_register_adapter(struct i2c_adapter *adap) bri->get_scl = get_scl_gpio_value; bri->set_scl = set_scl_gpio_value; - } else if (!bri->set_scl || !bri->get_scl) { + } else if (bri->recover_bus == i2c_generic_scl_recovery) { /* Generic SCL recovery */ - dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n"); - adap->bus_recovery_info = NULL; + if (!bri->set_scl || !bri->get_scl) { + dev_err(&adap->dev, "No {get|set}_scl() found, not using recovery\n"); + adap->bus_recovery_info = NULL; + } } } @@ -1817,6 +1816,8 @@ void i2c_del_adapter(struct i2c_adapter *adap) /* device name is gone after device_unregister */ dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name); + pm_runtime_disable(&adap->dev); + /* wait until all references to the device are gone * * FIXME: This is old code and should ideally be replaced by an @@ -1839,6 +1840,58 @@ void i2c_del_adapter(struct i2c_adapter *adap) } EXPORT_SYMBOL(i2c_del_adapter); +/** + * i2c_parse_fw_timings - get I2C related timing parameters from firmware + * @dev: The device to scan for I2C timing properties + * @t: the i2c_timings struct to be filled with values + * @use_defaults: bool to use sane defaults derived from the I2C specification + * when properties are not found, otherwise use 0 + * + * Scan the device for the generic I2C properties describing timing parameters + * for the signal and fill the given struct with the results. If a property was + * not found and use_defaults was true, then maximum timings are assumed which + * are derived from the I2C specification. If use_defaults is not used, the + * results will be 0, so drivers can apply their own defaults later. The latter + * is mainly intended for avoiding regressions of existing drivers which want + * to switch to this function. New drivers almost always should use the defaults. + */ + +void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults) +{ + int ret; + + memset(t, 0, sizeof(*t)); + + ret = device_property_read_u32(dev, "clock-frequency", &t->bus_freq_hz); + if (ret && use_defaults) + t->bus_freq_hz = 100000; + + ret = device_property_read_u32(dev, "i2c-scl-rising-time-ns", &t->scl_rise_ns); + if (ret && use_defaults) { + if (t->bus_freq_hz <= 100000) + t->scl_rise_ns = 1000; + else if (t->bus_freq_hz <= 400000) + t->scl_rise_ns = 300; + else + t->scl_rise_ns = 120; + } + + ret = device_property_read_u32(dev, "i2c-scl-falling-time-ns", &t->scl_fall_ns); + if (ret && use_defaults) { + if (t->bus_freq_hz <= 400000) + t->scl_fall_ns = 300; + else + t->scl_fall_ns = 120; + } + + device_property_read_u32(dev, "i2c-scl-internal-delay-ns", &t->scl_int_delay_ns); + + ret = device_property_read_u32(dev, "i2c-sda-falling-time-ns", &t->sda_fall_ns); + if (ret && use_defaults) + t->sda_fall_ns = t->scl_fall_ns; +} +EXPORT_SYMBOL_GPL(i2c_parse_fw_timings); + /* ------------------------------------------------------------------------- */ int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *)) @@ -1870,7 +1923,7 @@ int i2c_register_driver(struct module *owner, struct i2c_driver *driver) int res; /* Can't register until after driver model init */ - if (unlikely(WARN_ON(!i2c_bus_type.p))) + if (WARN_ON(!is_registered)) return -EAGAIN; /* add the driver to the list of i2c drivers in the driver core */ @@ -2048,6 +2101,9 @@ static int __init i2c_init(void) retval = bus_register(&i2c_bus_type); if (retval) return retval; + + is_registered = true; + #ifdef CONFIG_I2C_COMPAT i2c_adapter_compat_class = class_compat_register("i2c-adapter"); if (!i2c_adapter_compat_class) { @@ -2069,6 +2125,7 @@ class_err: class_compat_unregister(i2c_adapter_compat_class); bus_err: #endif + is_registered = false; bus_unregister(&i2c_bus_type); return retval; } @@ -2254,16 +2311,16 @@ int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) #endif if (in_atomic() || irqs_disabled()) { - ret = i2c_trylock_adapter(adap); + ret = adap->trylock_bus(adap, I2C_LOCK_SEGMENT); if (!ret) /* I2C activity is ongoing. */ return -EAGAIN; } else { - i2c_lock_adapter(adap); + i2c_lock_bus(adap, I2C_LOCK_SEGMENT); } ret = __i2c_transfer(adap, msgs, num); - i2c_unlock_adapter(adap); + i2c_unlock_bus(adap, I2C_LOCK_SEGMENT); return ret; } else { @@ -2591,7 +2648,7 @@ static u8 i2c_smbus_pec(u8 crc, u8 *p, size_t count) static u8 i2c_smbus_msg_pec(u8 pec, struct i2c_msg *msg) { /* The address will be sent first */ - u8 addr = (msg->addr << 1) | !!(msg->flags & I2C_M_RD); + u8 addr = i2c_8bit_addr_from_msg(msg); pec = i2c_smbus_pec(pec, &addr, 1); /* The data buffer follows */ @@ -3038,7 +3095,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB; if (adapter->algo->smbus_xfer) { - i2c_lock_adapter(adapter); + i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); /* Retry automatically on arbitration loss */ orig_jiffies = jiffies; @@ -3052,7 +3109,7 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, orig_jiffies + adapter->timeout)) break; } - i2c_unlock_adapter(adapter); + i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); if (res != -EOPNOTSUPP || !adapter->algo->master_xfer) goto trace; diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index 2413ec9..6ecfd76 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -22,17 +22,18 @@ /* The I2C_RDWR ioctl code is written by Kolja Waschk */ -#include -#include +#include #include -#include #include -#include -#include -#include -#include #include +#include +#include #include +#include +#include +#include +#include +#include #include /* @@ -47,9 +48,10 @@ struct i2c_dev { struct list_head list; struct i2c_adapter *adap; struct device *dev; + struct cdev cdev; }; -#define I2C_MINORS 256 +#define I2C_MINORS MINORMASK static LIST_HEAD(i2c_dev_list); static DEFINE_SPINLOCK(i2c_dev_list_lock); @@ -89,7 +91,7 @@ static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap) return i2c_dev; } -static void return_i2c_dev(struct i2c_dev *i2c_dev) +static void put_i2c_dev(struct i2c_dev *i2c_dev) { spin_lock(&i2c_dev_list_lock); list_del(&i2c_dev->list); @@ -552,6 +554,12 @@ static int i2cdev_attach_adapter(struct device *dev, void *dummy) if (IS_ERR(i2c_dev)) return PTR_ERR(i2c_dev); + cdev_init(&i2c_dev->cdev, &i2cdev_fops); + i2c_dev->cdev.owner = THIS_MODULE; + res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1); + if (res) + goto error_cdev; + /* register this i2c device with the driver core */ i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), NULL, @@ -565,7 +573,9 @@ static int i2cdev_attach_adapter(struct device *dev, void *dummy) adap->name, adap->nr); return 0; error: - return_i2c_dev(i2c_dev); + cdev_del(&i2c_dev->cdev); +error_cdev: + put_i2c_dev(i2c_dev); return res; } @@ -582,7 +592,8 @@ static int i2cdev_detach_adapter(struct device *dev, void *dummy) if (!i2c_dev) /* attach_adapter must have failed */ return 0; - return_i2c_dev(i2c_dev); + cdev_del(&i2c_dev->cdev); + put_i2c_dev(i2c_dev); device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr)); pr_debug("i2c-dev: adapter [%s] unregistered\n", adap->name); @@ -620,7 +631,7 @@ static int __init i2c_dev_init(void) printk(KERN_INFO "i2c /dev entries driver\n"); - res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); + res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c"); if (res) goto out; @@ -644,7 +655,7 @@ static int __init i2c_dev_init(void) out_unreg_class: class_destroy(i2c_dev_class); out_unreg_chrdev: - unregister_chrdev(I2C_MAJOR, "i2c"); + unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS); out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); return res; @@ -655,7 +666,7 @@ static void __exit i2c_dev_exit(void) bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier); i2c_for_each_dev(NULL, i2cdev_detach_adapter); class_destroy(i2c_dev_class); - unregister_chrdev(I2C_MAJOR, "i2c"); + unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS); } MODULE_AUTHOR("Frodo Looijaard and " diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c index 00fc5b1..8eee986 100644 --- a/drivers/i2c/i2c-mux.c +++ b/drivers/i2c/i2c-mux.c @@ -19,42 +19,78 @@ * warranty of any kind, whether express or implied. */ -#include -#include -#include +#include #include #include +#include +#include #include -#include +#include /* multiplexer per channel data */ struct i2c_mux_priv { struct i2c_adapter adap; struct i2c_algorithm algo; - - struct i2c_adapter *parent; - struct device *mux_dev; - void *mux_priv; + struct i2c_mux_core *muxc; u32 chan_id; - - int (*select)(struct i2c_adapter *, void *mux_priv, u32 chan_id); - int (*deselect)(struct i2c_adapter *, void *mux_priv, u32 chan_id); }; +static int __i2c_mux_master_xfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; + int ret; + + /* Switch to the right mux port and perform the transfer. */ + + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = __i2c_transfer(parent, msgs, num); + if (muxc->deselect) + muxc->deselect(muxc, priv->chan_id); + + return ret; +} + static int i2c_mux_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { struct i2c_mux_priv *priv = adap->algo_data; - struct i2c_adapter *parent = priv->parent; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; int ret; /* Switch to the right mux port and perform the transfer. */ - ret = priv->select(parent, priv->mux_priv, priv->chan_id); + ret = muxc->select(muxc, priv->chan_id); if (ret >= 0) - ret = __i2c_transfer(parent, msgs, num); - if (priv->deselect) - priv->deselect(parent, priv->mux_priv, priv->chan_id); + ret = i2c_transfer(parent, msgs, num); + if (muxc->deselect) + muxc->deselect(muxc, priv->chan_id); + + return ret; +} + +static int __i2c_mux_smbus_xfer(struct i2c_adapter *adap, + u16 addr, unsigned short flags, + char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct i2c_mux_priv *priv = adap->algo_data; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; + int ret; + + /* Select the right mux port and perform the transfer. */ + + ret = muxc->select(muxc, priv->chan_id); + if (ret >= 0) + ret = parent->algo->smbus_xfer(parent, addr, flags, + read_write, command, size, data); + if (muxc->deselect) + muxc->deselect(muxc, priv->chan_id); return ret; } @@ -65,17 +101,18 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap, int size, union i2c_smbus_data *data) { struct i2c_mux_priv *priv = adap->algo_data; - struct i2c_adapter *parent = priv->parent; + struct i2c_mux_core *muxc = priv->muxc; + struct i2c_adapter *parent = muxc->parent; int ret; /* Select the right mux port and perform the transfer. */ - ret = priv->select(parent, priv->mux_priv, priv->chan_id); + ret = muxc->select(muxc, priv->chan_id); if (ret >= 0) - ret = parent->algo->smbus_xfer(parent, addr, flags, - read_write, command, size, data); - if (priv->deselect) - priv->deselect(parent, priv->mux_priv, priv->chan_id); + ret = i2c_smbus_xfer(parent, addr, flags, + read_write, command, size, data); + if (muxc->deselect) + muxc->deselect(muxc, priv->chan_id); return ret; } @@ -84,7 +121,7 @@ static int i2c_mux_smbus_xfer(struct i2c_adapter *adap, static u32 i2c_mux_functionality(struct i2c_adapter *adap) { struct i2c_mux_priv *priv = adap->algo_data; - struct i2c_adapter *parent = priv->parent; + struct i2c_adapter *parent = priv->muxc->parent; return parent->algo->functionality(parent); } @@ -102,38 +139,167 @@ static unsigned int i2c_mux_parent_classes(struct i2c_adapter *parent) return class; } -struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent, - struct device *mux_dev, - void *mux_priv, u32 force_nr, u32 chan_id, - unsigned int class, - int (*select) (struct i2c_adapter *, - void *, u32), - int (*deselect) (struct i2c_adapter *, - void *, u32)) +static void i2c_mux_lock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + rt_mutex_lock(&parent->mux_lock); + if (!(flags & I2C_LOCK_ROOT_ADAPTER)) + return; + i2c_lock_bus(parent, flags); +} + +static int i2c_mux_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + if (!rt_mutex_trylock(&parent->mux_lock)) + return 0; /* mux_lock not locked, failure */ + if (!(flags & I2C_LOCK_ROOT_ADAPTER)) + return 1; /* we only want mux_lock, success */ + if (parent->trylock_bus(parent, flags)) + return 1; /* parent locked too, success */ + rt_mutex_unlock(&parent->mux_lock); + return 0; /* parent not locked, failure */ +} + +static void i2c_mux_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + if (flags & I2C_LOCK_ROOT_ADAPTER) + i2c_unlock_bus(parent, flags); + rt_mutex_unlock(&parent->mux_lock); +} + +static void i2c_parent_lock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + rt_mutex_lock(&parent->mux_lock); + i2c_lock_bus(parent, flags); +} + +static int i2c_parent_trylock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + if (!rt_mutex_trylock(&parent->mux_lock)) + return 0; /* mux_lock not locked, failure */ + if (parent->trylock_bus(parent, flags)) + return 1; /* parent locked too, success */ + rt_mutex_unlock(&parent->mux_lock); + return 0; /* parent not locked, failure */ +} + +static void i2c_parent_unlock_bus(struct i2c_adapter *adapter, + unsigned int flags) +{ + struct i2c_mux_priv *priv = adapter->algo_data; + struct i2c_adapter *parent = priv->muxc->parent; + + i2c_unlock_bus(parent, flags); + rt_mutex_unlock(&parent->mux_lock); +} + +struct i2c_adapter *i2c_root_adapter(struct device *dev) +{ + struct device *i2c; + struct i2c_adapter *i2c_root; + + /* + * Walk up the device tree to find an i2c adapter, indicating + * that this is an i2c client device. Check all ancestors to + * handle mfd devices etc. + */ + for (i2c = dev; i2c; i2c = i2c->parent) { + if (i2c->type == &i2c_adapter_type) + break; + } + if (!i2c) + return NULL; + + /* Continue up the tree to find the root i2c adapter */ + i2c_root = to_i2c_adapter(i2c); + while (i2c_parent_is_i2c_adapter(i2c_root)) + i2c_root = i2c_parent_is_i2c_adapter(i2c_root); + + return i2c_root; +} +EXPORT_SYMBOL_GPL(i2c_root_adapter); + +struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent, + struct device *dev, int max_adapters, + int sizeof_priv, u32 flags, + int (*select)(struct i2c_mux_core *, u32), + int (*deselect)(struct i2c_mux_core *, u32)) { + struct i2c_mux_core *muxc; + + muxc = devm_kzalloc(dev, sizeof(*muxc) + + max_adapters * sizeof(muxc->adapter[0]) + + sizeof_priv, GFP_KERNEL); + if (!muxc) + return NULL; + if (sizeof_priv) + muxc->priv = &muxc->adapter[max_adapters]; + + muxc->parent = parent; + muxc->dev = dev; + if (flags & I2C_MUX_LOCKED) + muxc->mux_locked = true; + muxc->select = select; + muxc->deselect = deselect; + muxc->max_adapters = max_adapters; + + return muxc; +} +EXPORT_SYMBOL_GPL(i2c_mux_alloc); + +int i2c_mux_add_adapter(struct i2c_mux_core *muxc, + u32 force_nr, u32 chan_id, + unsigned int class) +{ + struct i2c_adapter *parent = muxc->parent; struct i2c_mux_priv *priv; char symlink_name[20]; int ret; - priv = kzalloc(sizeof(struct i2c_mux_priv), GFP_KERNEL); + if (muxc->num_adapters >= muxc->max_adapters) { + dev_err(muxc->dev, "No room for more i2c-mux adapters\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) - return NULL; + return -ENOMEM; /* Set up private adapter data */ - priv->parent = parent; - priv->mux_dev = mux_dev; - priv->mux_priv = mux_priv; + priv->muxc = muxc; priv->chan_id = chan_id; - priv->select = select; - priv->deselect = deselect; /* Need to do algo dynamically because we don't know ahead * of time what sort of physical adapter we'll be dealing with. */ - if (parent->algo->master_xfer) - priv->algo.master_xfer = i2c_mux_master_xfer; - if (parent->algo->smbus_xfer) - priv->algo.smbus_xfer = i2c_mux_smbus_xfer; + if (parent->algo->master_xfer) { + if (muxc->mux_locked) + priv->algo.master_xfer = i2c_mux_master_xfer; + else + priv->algo.master_xfer = __i2c_mux_master_xfer; + } + if (parent->algo->smbus_xfer) { + if (muxc->mux_locked) + priv->algo.smbus_xfer = i2c_mux_smbus_xfer; + else + priv->algo.smbus_xfer = __i2c_mux_smbus_xfer; + } priv->algo.functionality = i2c_mux_functionality; /* Now fill out new adapter structure */ @@ -146,6 +312,15 @@ struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent, priv->adap.retries = parent->retries; priv->adap.timeout = parent->timeout; priv->adap.quirks = parent->quirks; + if (muxc->mux_locked) { + priv->adap.lock_bus = i2c_mux_lock_bus; + priv->adap.trylock_bus = i2c_mux_trylock_bus; + priv->adap.unlock_bus = i2c_mux_unlock_bus; + } else { + priv->adap.lock_bus = i2c_parent_lock_bus; + priv->adap.trylock_bus = i2c_parent_trylock_bus; + priv->adap.unlock_bus = i2c_parent_unlock_bus; + } /* Sanity check on class */ if (i2c_mux_parent_classes(parent) & class) @@ -159,11 +334,11 @@ struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent, * Try to populate the mux adapter's of_node, expands to * nothing if !CONFIG_OF. */ - if (mux_dev->of_node) { + if (muxc->dev->of_node) { struct device_node *child; u32 reg; - for_each_child_of_node(mux_dev->of_node, child) { + for_each_child_of_node(muxc->dev->of_node, child) { ret = of_property_read_u32(child, "reg", ®); if (ret) continue; @@ -177,8 +352,9 @@ struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent, /* * Associate the mux channel with an ACPI node. */ - if (has_acpi_companion(mux_dev)) - acpi_preset_companion(&priv->adap.dev, ACPI_COMPANION(mux_dev), + if (has_acpi_companion(muxc->dev)) + acpi_preset_companion(&priv->adap.dev, + ACPI_COMPANION(muxc->dev), chan_id); if (force_nr) { @@ -192,35 +368,45 @@ struct i2c_adapter *i2c_add_mux_adapter(struct i2c_adapter *parent, "failed to add mux-adapter (error=%d)\n", ret); kfree(priv); - return NULL; + return ret; } - WARN(sysfs_create_link(&priv->adap.dev.kobj, &mux_dev->kobj, "mux_device"), - "can't create symlink to mux device\n"); + WARN(sysfs_create_link(&priv->adap.dev.kobj, &muxc->dev->kobj, + "mux_device"), + "can't create symlink to mux device\n"); snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id); - WARN(sysfs_create_link(&mux_dev->kobj, &priv->adap.dev.kobj, symlink_name), - "can't create symlink for channel %u\n", chan_id); + WARN(sysfs_create_link(&muxc->dev->kobj, &priv->adap.dev.kobj, + symlink_name), + "can't create symlink for channel %u\n", chan_id); dev_info(&parent->dev, "Added multiplexed i2c bus %d\n", i2c_adapter_id(&priv->adap)); - return &priv->adap; + muxc->adapter[muxc->num_adapters++] = &priv->adap; + return 0; } -EXPORT_SYMBOL_GPL(i2c_add_mux_adapter); +EXPORT_SYMBOL_GPL(i2c_mux_add_adapter); -void i2c_del_mux_adapter(struct i2c_adapter *adap) +void i2c_mux_del_adapters(struct i2c_mux_core *muxc) { - struct i2c_mux_priv *priv = adap->algo_data; char symlink_name[20]; - snprintf(symlink_name, sizeof(symlink_name), "channel-%u", priv->chan_id); - sysfs_remove_link(&priv->mux_dev->kobj, symlink_name); + while (muxc->num_adapters) { + struct i2c_adapter *adap = muxc->adapter[--muxc->num_adapters]; + struct i2c_mux_priv *priv = adap->algo_data; + + muxc->adapter[muxc->num_adapters] = NULL; + + snprintf(symlink_name, sizeof(symlink_name), + "channel-%u", priv->chan_id); + sysfs_remove_link(&muxc->dev->kobj, symlink_name); - sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); - i2c_del_adapter(adap); - kfree(priv); + sysfs_remove_link(&priv->adap.dev.kobj, "mux_device"); + i2c_del_adapter(adap); + kfree(priv); + } } -EXPORT_SYMBOL_GPL(i2c_del_mux_adapter); +EXPORT_SYMBOL_GPL(i2c_mux_del_adapters); MODULE_AUTHOR("Rodolfo Giometti "); MODULE_DESCRIPTION("I2C driver for multiplexed I2C busses"); diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c index 94765a8..abb55d3 100644 --- a/drivers/i2c/i2c-smbus.c +++ b/drivers/i2c/i2c-smbus.c @@ -15,14 +15,14 @@ * GNU General Public License for more details. */ -#include -#include #include -#include -#include #include #include +#include +#include +#include #include +#include struct i2c_smbus_alert { unsigned int alert_edge_triggered:1; diff --git a/drivers/i2c/i2c-stub.c b/drivers/i2c/i2c-stub.c index af2a94e..06af583 100644 --- a/drivers/i2c/i2c-stub.c +++ b/drivers/i2c/i2c-stub.c @@ -17,13 +17,13 @@ #define DEBUG 1 -#include -#include -#include -#include #include #include +#include +#include #include +#include +#include #define MAX_CHIPS 10 diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index f06b0e2..e280c8e 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig @@ -72,4 +72,13 @@ config I2C_MUX_REG This driver can also be built as a module. If so, the module will be called i2c-mux-reg. +config I2C_DEMUX_PINCTRL + tristate "pinctrl-based I2C demultiplexer" + depends on PINCTRL && OF + select OF_DYNAMIC + help + If you say yes to this option, support will be included for an I2C + demultiplexer that uses the pinctrl subsystem. This is useful if you + want to change the I2C master at run-time depending on features. + endmenu diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index e89799b..7c267c2 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile @@ -3,6 +3,8 @@ obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o +obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o + obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o diff --git a/drivers/i2c/muxes/i2c-arb-gpio-challenge.c b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c index 402e3a6..a90bbc4 100644 --- a/drivers/i2c/muxes/i2c-arb-gpio-challenge.c +++ b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c @@ -28,8 +28,6 @@ /** * struct i2c_arbitrator_data - Driver data for I2C arbitrator * - * @parent: Parent adapter - * @child: Child bus * @our_gpio: GPIO we'll use to claim. * @our_gpio_release: 0 if active high; 1 if active low; AKA if the GPIO == * this then consider it released. @@ -42,8 +40,6 @@ */ struct i2c_arbitrator_data { - struct i2c_adapter *parent; - struct i2c_adapter *child; int our_gpio; int our_gpio_release; int their_gpio; @@ -59,9 +55,9 @@ struct i2c_arbitrator_data { * * Use the GPIO-based signalling protocol; return -EBUSY if we fail. */ -static int i2c_arbitrator_select(struct i2c_adapter *adap, void *data, u32 chan) +static int i2c_arbitrator_select(struct i2c_mux_core *muxc, u32 chan) { - const struct i2c_arbitrator_data *arb = data; + const struct i2c_arbitrator_data *arb = i2c_mux_priv(muxc); unsigned long stop_retry, stop_time; /* Start a round of trying to claim the bus */ @@ -93,7 +89,7 @@ static int i2c_arbitrator_select(struct i2c_adapter *adap, void *data, u32 chan) /* Give up, release our claim */ gpio_set_value(arb->our_gpio, arb->our_gpio_release); udelay(arb->slew_delay_us); - dev_err(&adap->dev, "Could not claim bus, timeout\n"); + dev_err(muxc->dev, "Could not claim bus, timeout\n"); return -EBUSY; } @@ -102,10 +98,9 @@ static int i2c_arbitrator_select(struct i2c_adapter *adap, void *data, u32 chan) * * Release the I2C bus using the GPIO-based signalling protocol. */ -static int i2c_arbitrator_deselect(struct i2c_adapter *adap, void *data, - u32 chan) +static int i2c_arbitrator_deselect(struct i2c_mux_core *muxc, u32 chan) { - const struct i2c_arbitrator_data *arb = data; + const struct i2c_arbitrator_data *arb = i2c_mux_priv(muxc); /* Release the bus and wait for the other master to notice */ gpio_set_value(arb->our_gpio, arb->our_gpio_release); @@ -119,6 +114,7 @@ static int i2c_arbitrator_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; struct device_node *parent_np; + struct i2c_mux_core *muxc; struct i2c_arbitrator_data *arb; enum of_gpio_flags gpio_flags; unsigned long out_init; @@ -134,12 +130,13 @@ static int i2c_arbitrator_probe(struct platform_device *pdev) return -EINVAL; } - arb = devm_kzalloc(dev, sizeof(*arb), GFP_KERNEL); - if (!arb) { - dev_err(dev, "Cannot allocate i2c_arbitrator_data\n"); + muxc = i2c_mux_alloc(NULL, dev, 1, sizeof(*arb), 0, + i2c_arbitrator_select, i2c_arbitrator_deselect); + if (!muxc) return -ENOMEM; - } - platform_set_drvdata(pdev, arb); + arb = i2c_mux_priv(muxc); + + platform_set_drvdata(pdev, muxc); /* Request GPIOs */ ret = of_get_named_gpio_flags(np, "our-claim-gpio", 0, &gpio_flags); @@ -196,21 +193,18 @@ static int i2c_arbitrator_probe(struct platform_device *pdev) dev_err(dev, "Cannot parse i2c-parent\n"); return -EINVAL; } - arb->parent = of_get_i2c_adapter_by_node(parent_np); + muxc->parent = of_get_i2c_adapter_by_node(parent_np); of_node_put(parent_np); - if (!arb->parent) { + if (!muxc->parent) { dev_err(dev, "Cannot find parent bus\n"); return -EPROBE_DEFER; } /* Actually add the mux adapter */ - arb->child = i2c_add_mux_adapter(arb->parent, dev, arb, 0, 0, 0, - i2c_arbitrator_select, - i2c_arbitrator_deselect); - if (!arb->child) { + ret = i2c_mux_add_adapter(muxc, 0, 0, 0); + if (ret) { dev_err(dev, "Failed to add adapter\n"); - ret = -ENODEV; - i2c_put_adapter(arb->parent); + i2c_put_adapter(muxc->parent); } return ret; @@ -218,11 +212,10 @@ static int i2c_arbitrator_probe(struct platform_device *pdev) static int i2c_arbitrator_remove(struct platform_device *pdev) { - struct i2c_arbitrator_data *arb = platform_get_drvdata(pdev); - - i2c_del_mux_adapter(arb->child); - i2c_put_adapter(arb->parent); + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); return 0; } diff --git b/drivers/i2c/muxes/i2c-demux-pinctrl.c b/drivers/i2c/muxes/i2c-demux-pinctrl.c new file mode 100644 index 0000000..215ac87 --- /dev/null +++ b/drivers/i2c/muxes/i2c-demux-pinctrl.c @@ -0,0 +1,293 @@ +/* + * Pinctrl based I2C DeMultiplexer + * + * Copyright (C) 2015-16 by Wolfram Sang, Sang Engineering + * Copyright (C) 2015-16 by Renesas Electronics 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; version 2 of the License. + * + * See the bindings doc for DTS setup and the sysfs doc for usage information. + * (look for filenames containing 'i2c-demux-pinctrl' in Documentation/) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct i2c_demux_pinctrl_chan { + struct device_node *parent_np; + struct i2c_adapter *parent_adap; + struct of_changeset chgset; +}; + +struct i2c_demux_pinctrl_priv { + int cur_chan; + int num_chan; + struct device *dev; + const char *bus_name; + struct i2c_adapter cur_adap; + struct i2c_algorithm algo; + struct i2c_demux_pinctrl_chan chan[]; +}; + +static struct property status_okay = { .name = "status", .length = 3, .value = "ok" }; + +static int i2c_demux_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct i2c_demux_pinctrl_priv *priv = adap->algo_data; + struct i2c_adapter *parent = priv->chan[priv->cur_chan].parent_adap; + + return __i2c_transfer(parent, msgs, num); +} + +static u32 i2c_demux_functionality(struct i2c_adapter *adap) +{ + struct i2c_demux_pinctrl_priv *priv = adap->algo_data; + struct i2c_adapter *parent = priv->chan[priv->cur_chan].parent_adap; + + return parent->algo->functionality(parent); +} + +static int i2c_demux_activate_master(struct i2c_demux_pinctrl_priv *priv, u32 new_chan) +{ + struct i2c_adapter *adap; + struct pinctrl *p; + int ret; + + ret = of_changeset_apply(&priv->chan[new_chan].chgset); + if (ret) + goto err; + + adap = of_find_i2c_adapter_by_node(priv->chan[new_chan].parent_np); + if (!adap) { + ret = -ENODEV; + goto err_with_revert; + } + + p = devm_pinctrl_get_select(adap->dev.parent, priv->bus_name); + if (IS_ERR(p)) { + ret = PTR_ERR(p); + goto err_with_put; + } + + priv->chan[new_chan].parent_adap = adap; + priv->cur_chan = new_chan; + + /* Now fill out current adapter structure. cur_chan must be up to date */ + priv->algo.master_xfer = i2c_demux_master_xfer; + priv->algo.functionality = i2c_demux_functionality; + + snprintf(priv->cur_adap.name, sizeof(priv->cur_adap.name), + "i2c-demux (master i2c-%d)", i2c_adapter_id(adap)); + priv->cur_adap.owner = THIS_MODULE; + priv->cur_adap.algo = &priv->algo; + priv->cur_adap.algo_data = priv; + priv->cur_adap.dev.parent = priv->dev; + priv->cur_adap.class = adap->class; + priv->cur_adap.retries = adap->retries; + priv->cur_adap.timeout = adap->timeout; + priv->cur_adap.quirks = adap->quirks; + priv->cur_adap.dev.of_node = priv->dev->of_node; + ret = i2c_add_adapter(&priv->cur_adap); + if (ret < 0) + goto err_with_put; + + return 0; + + err_with_put: + i2c_put_adapter(adap); + err_with_revert: + of_changeset_revert(&priv->chan[new_chan].chgset); + err: + dev_err(priv->dev, "failed to setup demux-adapter %d (%d)\n", new_chan, ret); + return ret; +} + +static int i2c_demux_deactivate_master(struct i2c_demux_pinctrl_priv *priv) +{ + int ret, cur = priv->cur_chan; + + if (cur < 0) + return 0; + + i2c_del_adapter(&priv->cur_adap); + i2c_put_adapter(priv->chan[cur].parent_adap); + + ret = of_changeset_revert(&priv->chan[cur].chgset); + + priv->chan[cur].parent_adap = NULL; + priv->cur_chan = -EINVAL; + + return ret; +} + +static int i2c_demux_change_master(struct i2c_demux_pinctrl_priv *priv, u32 new_chan) +{ + int ret; + + if (new_chan == priv->cur_chan) + return 0; + + ret = i2c_demux_deactivate_master(priv); + if (ret) + return ret; + + return i2c_demux_activate_master(priv, new_chan); +} + +static ssize_t available_masters_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + int count = 0, i; + + for (i = 0; i < priv->num_chan && count < PAGE_SIZE; i++) + count += scnprintf(buf + count, PAGE_SIZE - count, "%d:%s%c", + i, priv->chan[i].parent_np->full_name, + i == priv->num_chan - 1 ? '\n' : ' '); + + return count; +} +static DEVICE_ATTR_RO(available_masters); + +static ssize_t current_master_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", priv->cur_chan); +} + +static ssize_t current_master_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret < 0) + return ret; + + if (val >= priv->num_chan) + return -EINVAL; + + ret = i2c_demux_change_master(priv, val); + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR_RW(current_master); + +static int i2c_demux_pinctrl_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct i2c_demux_pinctrl_priv *priv; + int num_chan, i, j, err; + + num_chan = of_count_phandle_with_args(np, "i2c-parent", NULL); + if (num_chan < 2) { + dev_err(&pdev->dev, "Need at least two I2C masters to switch\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv) + + num_chan * sizeof(struct i2c_demux_pinctrl_chan), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + err = of_property_read_string(np, "i2c-bus-name", &priv->bus_name); + if (err) + return err; + + for (i = 0; i < num_chan; i++) { + struct device_node *adap_np; + + adap_np = of_parse_phandle(np, "i2c-parent", i); + if (!adap_np) { + dev_err(&pdev->dev, "can't get phandle for parent %d\n", i); + err = -ENOENT; + goto err_rollback; + } + priv->chan[i].parent_np = adap_np; + + of_changeset_init(&priv->chan[i].chgset); + of_changeset_update_property(&priv->chan[i].chgset, adap_np, &status_okay); + } + + priv->num_chan = num_chan; + priv->dev = &pdev->dev; + + platform_set_drvdata(pdev, priv); + + /* switch to first parent as active master */ + i2c_demux_activate_master(priv, 0); + + err = device_create_file(&pdev->dev, &dev_attr_available_masters); + if (err) + goto err_rollback; + + err = device_create_file(&pdev->dev, &dev_attr_current_master); + if (err) + goto err_rollback_available; + + return 0; + +err_rollback_available: + device_remove_file(&pdev->dev, &dev_attr_available_masters); +err_rollback: + for (j = 0; j < i; j++) { + of_node_put(priv->chan[j].parent_np); + of_changeset_destroy(&priv->chan[j].chgset); + } + + return err; +} + +static int i2c_demux_pinctrl_remove(struct platform_device *pdev) +{ + struct i2c_demux_pinctrl_priv *priv = platform_get_drvdata(pdev); + int i; + + device_remove_file(&pdev->dev, &dev_attr_current_master); + device_remove_file(&pdev->dev, &dev_attr_available_masters); + + i2c_demux_deactivate_master(priv); + + for (i = 0; i < priv->num_chan; i++) { + of_node_put(priv->chan[i].parent_np); + of_changeset_destroy(&priv->chan[i].chgset); + } + + return 0; +} + +static const struct of_device_id i2c_demux_pinctrl_of_match[] = { + { .compatible = "i2c-demux-pinctrl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_demux_pinctrl_of_match); + +static struct platform_driver i2c_demux_pinctrl_driver = { + .driver = { + .name = "i2c-demux-pinctrl", + .of_match_table = i2c_demux_pinctrl_of_match, + }, + .probe = i2c_demux_pinctrl_probe, + .remove = i2c_demux_pinctrl_remove, +}; +module_platform_driver(i2c_demux_pinctrl_driver); + +MODULE_DESCRIPTION("pinctrl-based I2C demux driver"); +MODULE_AUTHOR("Wolfram Sang "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-demux-pinctrl"); diff --git a/drivers/i2c/muxes/i2c-mux-gpio.c b/drivers/i2c/muxes/i2c-mux-gpio.c index b8e11c1..e5cf26e 100644 --- a/drivers/i2c/muxes/i2c-mux-gpio.c +++ b/drivers/i2c/muxes/i2c-mux-gpio.c @@ -15,11 +15,10 @@ #include #include #include +#include "../../gpio/gpiolib.h" #include struct gpiomux { - struct i2c_adapter *parent; - struct i2c_adapter **adap; /* child busses */ struct i2c_mux_gpio_platform_data data; unsigned gpio_base; }; @@ -33,18 +32,18 @@ static void i2c_mux_gpio_set(const struct gpiomux *mux, unsigned val) val & (1 << i)); } -static int i2c_mux_gpio_select(struct i2c_adapter *adap, void *data, u32 chan) +static int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan) { - struct gpiomux *mux = data; + struct gpiomux *mux = i2c_mux_priv(muxc); i2c_mux_gpio_set(mux, chan); return 0; } -static int i2c_mux_gpio_deselect(struct i2c_adapter *adap, void *data, u32 chan) +static int i2c_mux_gpio_deselect(struct i2c_mux_core *muxc, u32 chan) { - struct gpiomux *mux = data; + struct gpiomux *mux = i2c_mux_priv(muxc); i2c_mux_gpio_set(mux, mux->data.idle); @@ -136,19 +135,16 @@ static int i2c_mux_gpio_probe_dt(struct gpiomux *mux, static int i2c_mux_gpio_probe(struct platform_device *pdev) { + struct i2c_mux_core *muxc; struct gpiomux *mux; struct i2c_adapter *parent; - int (*deselect) (struct i2c_adapter *, void *, u32); + struct i2c_adapter *root; unsigned initial_state, gpio_base; int i, ret; mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); - if (!mux) { - dev_err(&pdev->dev, "Cannot allocate gpiomux structure"); + if (!mux) return -ENOMEM; - } - - platform_set_drvdata(pdev, mux); if (!dev_get_platdata(&pdev->dev)) { ret = i2c_mux_gpio_probe_dt(mux, pdev); @@ -180,27 +176,32 @@ static int i2c_mux_gpio_probe(struct platform_device *pdev) if (!parent) return -EPROBE_DEFER; - mux->parent = parent; - mux->gpio_base = gpio_base; - - mux->adap = devm_kzalloc(&pdev->dev, - sizeof(*mux->adap) * mux->data.n_values, - GFP_KERNEL); - if (!mux->adap) { - dev_err(&pdev->dev, "Cannot allocate i2c_adapter structure"); + muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 0, 0, + i2c_mux_gpio_select, NULL); + if (!muxc) { ret = -ENOMEM; goto alloc_failed; } + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); + + root = i2c_root_adapter(&parent->dev); + + muxc->mux_locked = true; + mux->gpio_base = gpio_base; if (mux->data.idle != I2C_MUX_GPIO_NO_IDLE) { initial_state = mux->data.idle; - deselect = i2c_mux_gpio_deselect; + muxc->deselect = i2c_mux_gpio_deselect; } else { initial_state = mux->data.values[0]; - deselect = NULL; } for (i = 0; i < mux->data.n_gpios; i++) { + struct device *gpio_dev; + struct gpio_desc *gpio_desc; + ret = gpio_request(gpio_base + mux->data.gpios[i], "i2c-mux-gpio"); if (ret) { dev_err(&pdev->dev, "Failed to request GPIO %d\n", @@ -217,17 +218,24 @@ static int i2c_mux_gpio_probe(struct platform_device *pdev) i++; /* gpio_request above succeeded, so must free */ goto err_request_gpio; } + + if (!muxc->mux_locked) + continue; + + gpio_desc = gpio_to_desc(gpio_base + mux->data.gpios[i]); + gpio_dev = &gpio_desc->gdev->dev; + muxc->mux_locked = i2c_root_adapter(gpio_dev) == root; } + if (muxc->mux_locked) + dev_info(&pdev->dev, "mux-locked i2c mux\n"); + for (i = 0; i < mux->data.n_values; i++) { u32 nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; unsigned int class = mux->data.classes ? mux->data.classes[i] : 0; - mux->adap[i] = i2c_add_mux_adapter(parent, &pdev->dev, mux, nr, - mux->data.values[i], class, - i2c_mux_gpio_select, deselect); - if (!mux->adap[i]) { - ret = -ENODEV; + ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); + if (ret) { dev_err(&pdev->dev, "Failed to add adapter %d\n", i); goto add_adapter_failed; } @@ -239,8 +247,7 @@ static int i2c_mux_gpio_probe(struct platform_device *pdev) return 0; add_adapter_failed: - for (; i > 0; i--) - i2c_del_mux_adapter(mux->adap[i - 1]); + i2c_mux_del_adapters(muxc); i = mux->data.n_gpios; err_request_gpio: for (; i > 0; i--) @@ -253,16 +260,16 @@ alloc_failed: static int i2c_mux_gpio_remove(struct platform_device *pdev) { - struct gpiomux *mux = platform_get_drvdata(pdev); + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + struct gpiomux *mux = i2c_mux_priv(muxc); int i; - for (i = 0; i < mux->data.n_values; i++) - i2c_del_mux_adapter(mux->adap[i]); + i2c_mux_del_adapters(muxc); for (i = 0; i < mux->data.n_gpios; i++) gpio_free(mux->gpio_base + mux->data.gpios[i]); - i2c_put_adapter(mux->parent); + i2c_put_adapter(muxc->parent); return 0; } diff --git a/drivers/i2c/muxes/i2c-mux-pca9541.c b/drivers/i2c/muxes/i2c-mux-pca9541.c index d0ba424..3cb8af6 100644 --- a/drivers/i2c/muxes/i2c-mux-pca9541.c +++ b/drivers/i2c/muxes/i2c-mux-pca9541.c @@ -73,7 +73,7 @@ #define SELECT_DELAY_LONG 1000 struct pca9541 { - struct i2c_adapter *mux_adap; + struct i2c_client *client; unsigned long select_timeout; unsigned long arb_timeout; }; @@ -217,7 +217,8 @@ static const u8 pca9541_control[16] = { */ static int pca9541_arbitrate(struct i2c_client *client) { - struct pca9541 *data = i2c_get_clientdata(client); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca9541 *data = i2c_mux_priv(muxc); int reg; reg = pca9541_reg_read(client, PCA9541_CONTROL); @@ -285,9 +286,10 @@ static int pca9541_arbitrate(struct i2c_client *client) return 0; } -static int pca9541_select_chan(struct i2c_adapter *adap, void *client, u32 chan) +static int pca9541_select_chan(struct i2c_mux_core *muxc, u32 chan) { - struct pca9541 *data = i2c_get_clientdata(client); + struct pca9541 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; int ret; unsigned long timeout = jiffies + ARB2_TIMEOUT; /* give up after this time */ @@ -309,9 +311,11 @@ static int pca9541_select_chan(struct i2c_adapter *adap, void *client, u32 chan) return -ETIMEDOUT; } -static int pca9541_release_chan(struct i2c_adapter *adap, - void *client, u32 chan) +static int pca9541_release_chan(struct i2c_mux_core *muxc, u32 chan) { + struct pca9541 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + pca9541_release_bus(client); return 0; } @@ -324,20 +328,13 @@ static int pca9541_probe(struct i2c_client *client, { struct i2c_adapter *adap = client->adapter; struct pca954x_platform_data *pdata = dev_get_platdata(&client->dev); + struct i2c_mux_core *muxc; struct pca9541 *data; int force; - int ret = -ENODEV; + int ret; if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE_DATA)) - goto err; - - data = kzalloc(sizeof(struct pca9541), GFP_KERNEL); - if (!data) { - ret = -ENOMEM; - goto err; - } - - i2c_set_clientdata(client, data); + return -ENODEV; /* * I2C accesses are unprotected here. @@ -352,34 +349,33 @@ static int pca9541_probe(struct i2c_client *client, force = 0; if (pdata) force = pdata->modes[0].adap_id; - data->mux_adap = i2c_add_mux_adapter(adap, &client->dev, client, - force, 0, 0, - pca9541_select_chan, - pca9541_release_chan); + muxc = i2c_mux_alloc(adap, &client->dev, 1, sizeof(*data), 0, + pca9541_select_chan, pca9541_release_chan); + if (!muxc) + return -ENOMEM; - if (data->mux_adap == NULL) { + data = i2c_mux_priv(muxc); + data->client = client; + + i2c_set_clientdata(client, muxc); + + ret = i2c_mux_add_adapter(muxc, force, 0, 0); + if (ret) { dev_err(&client->dev, "failed to register master selector\n"); - goto exit_free; + return ret; } dev_info(&client->dev, "registered master selector for I2C %s\n", client->name); return 0; - -exit_free: - kfree(data); -err: - return ret; } static int pca9541_remove(struct i2c_client *client) { - struct pca9541 *data = i2c_get_clientdata(client); - - i2c_del_mux_adapter(data->mux_adap); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); - kfree(data); + i2c_mux_del_adapters(muxc); return 0; } diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c index acfcef3..528e755 100644 --- a/drivers/i2c/muxes/i2c-mux-pca954x.c +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c @@ -60,9 +60,10 @@ enum pca_type { struct pca954x { enum pca_type type; - struct i2c_adapter *virt_adaps[PCA954X_MAX_NCHANS]; u8 last_chan; /* last register value */ + u8 deselect; + struct i2c_client *client; }; struct chip_desc { @@ -146,10 +147,10 @@ static int pca954x_reg_write(struct i2c_adapter *adap, return ret; } -static int pca954x_select_chan(struct i2c_adapter *adap, - void *client, u32 chan) +static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan) { - struct pca954x *data = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; const struct chip_desc *chip = &chips[data->type]; u8 regval; int ret = 0; @@ -162,21 +163,24 @@ static int pca954x_select_chan(struct i2c_adapter *adap, /* Only select the channel if its different from the last channel */ if (data->last_chan != regval) { - ret = pca954x_reg_write(adap, client, regval); + ret = pca954x_reg_write(muxc->parent, client, regval); data->last_chan = regval; } return ret; } -static int pca954x_deselect_mux(struct i2c_adapter *adap, - void *client, u32 chan) +static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan) { - struct pca954x *data = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + + if (!(data->deselect & (1 << chan))) + return 0; /* Deselect active channel */ data->last_chan = 0; - return pca954x_reg_write(adap, client, data->last_chan); + return pca954x_reg_write(muxc->parent, client, data->last_chan); } /* @@ -191,17 +195,22 @@ static int pca954x_probe(struct i2c_client *client, bool idle_disconnect_dt; struct gpio_desc *gpio; int num, force, class; + struct i2c_mux_core *muxc; struct pca954x *data; int ret; if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) return -ENODEV; - data = devm_kzalloc(&client->dev, sizeof(struct pca954x), GFP_KERNEL); - if (!data) + muxc = i2c_mux_alloc(adap, &client->dev, + PCA954X_MAX_NCHANS, sizeof(*data), 0, + pca954x_select_chan, pca954x_deselect_mux); + if (!muxc) return -ENOMEM; + data = i2c_mux_priv(muxc); - i2c_set_clientdata(client, data); + i2c_set_clientdata(client, muxc); + data->client = client; /* Get the mux out of reset if a reset GPIO is specified. */ gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_LOW); @@ -238,16 +247,13 @@ static int pca954x_probe(struct i2c_client *client, /* discard unconfigured channels */ break; idle_disconnect_pd = pdata->modes[num].deselect_on_exit; + data->deselect |= (idle_disconnect_pd + || idle_disconnect_dt) << num; } - data->virt_adaps[num] = - i2c_add_mux_adapter(adap, &client->dev, client, - force, num, class, pca954x_select_chan, - (idle_disconnect_pd || idle_disconnect_dt) - ? pca954x_deselect_mux : NULL); + ret = i2c_mux_add_adapter(muxc, force, num, class); - if (data->virt_adaps[num] == NULL) { - ret = -ENODEV; + if (ret) { dev_err(&client->dev, "failed to register multiplexed adapter" " %d as bus %d\n", num, force); @@ -263,23 +269,15 @@ static int pca954x_probe(struct i2c_client *client, return 0; virt_reg_failed: - for (num--; num >= 0; num--) - i2c_del_mux_adapter(data->virt_adaps[num]); + i2c_mux_del_adapters(muxc); return ret; } static int pca954x_remove(struct i2c_client *client) { - struct pca954x *data = i2c_get_clientdata(client); - const struct chip_desc *chip = &chips[data->type]; - int i; - - for (i = 0; i < chip->nchans; ++i) - if (data->virt_adaps[i]) { - i2c_del_mux_adapter(data->virt_adaps[i]); - data->virt_adaps[i] = NULL; - } + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + i2c_mux_del_adapters(muxc); return 0; } @@ -287,7 +285,8 @@ static int pca954x_remove(struct i2c_client *client) static int pca954x_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - struct pca954x *data = i2c_get_clientdata(client); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); data->last_chan = 0; return i2c_smbus_write_byte(client, 0); diff --git a/drivers/i2c/muxes/i2c-mux-pinctrl.c b/drivers/i2c/muxes/i2c-mux-pinctrl.c index b5a982b..35bb775 100644 --- a/drivers/i2c/muxes/i2c-mux-pinctrl.c +++ b/drivers/i2c/muxes/i2c-mux-pinctrl.c @@ -24,36 +24,32 @@ #include #include #include +#include "../../pinctrl/core.h" struct i2c_mux_pinctrl { - struct device *dev; struct i2c_mux_pinctrl_platform_data *pdata; struct pinctrl *pinctrl; struct pinctrl_state **states; struct pinctrl_state *state_idle; - struct i2c_adapter *parent; - struct i2c_adapter **busses; }; -static int i2c_mux_pinctrl_select(struct i2c_adapter *adap, void *data, - u32 chan) +static int i2c_mux_pinctrl_select(struct i2c_mux_core *muxc, u32 chan) { - struct i2c_mux_pinctrl *mux = data; + struct i2c_mux_pinctrl *mux = i2c_mux_priv(muxc); return pinctrl_select_state(mux->pinctrl, mux->states[chan]); } -static int i2c_mux_pinctrl_deselect(struct i2c_adapter *adap, void *data, - u32 chan) +static int i2c_mux_pinctrl_deselect(struct i2c_mux_core *muxc, u32 chan) { - struct i2c_mux_pinctrl *mux = data; + struct i2c_mux_pinctrl *mux = i2c_mux_priv(muxc); return pinctrl_select_state(mux->pinctrl, mux->state_idle); } #ifdef CONFIG_OF static int i2c_mux_pinctrl_parse_dt(struct i2c_mux_pinctrl *mux, - struct platform_device *pdev) + struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; int num_names, i, ret; @@ -64,15 +60,12 @@ static int i2c_mux_pinctrl_parse_dt(struct i2c_mux_pinctrl *mux, return 0; mux->pdata = devm_kzalloc(&pdev->dev, sizeof(*mux->pdata), GFP_KERNEL); - if (!mux->pdata) { - dev_err(mux->dev, - "Cannot allocate i2c_mux_pinctrl_platform_data\n"); + if (!mux->pdata) return -ENOMEM; - } num_names = of_property_count_strings(np, "pinctrl-names"); if (num_names < 0) { - dev_err(mux->dev, "Cannot parse pinctrl-names: %d\n", + dev_err(&pdev->dev, "Cannot parse pinctrl-names: %d\n", num_names); return num_names; } @@ -80,23 +73,22 @@ static int i2c_mux_pinctrl_parse_dt(struct i2c_mux_pinctrl *mux, mux->pdata->pinctrl_states = devm_kzalloc(&pdev->dev, sizeof(*mux->pdata->pinctrl_states) * num_names, GFP_KERNEL); - if (!mux->pdata->pinctrl_states) { - dev_err(mux->dev, "Cannot allocate pinctrl_states\n"); + if (!mux->pdata->pinctrl_states) return -ENOMEM; - } for (i = 0; i < num_names; i++) { ret = of_property_read_string_index(np, "pinctrl-names", i, &mux->pdata->pinctrl_states[mux->pdata->bus_count]); if (ret < 0) { - dev_err(mux->dev, "Cannot parse pinctrl-names: %d\n", + dev_err(&pdev->dev, "Cannot parse pinctrl-names: %d\n", ret); return ret; } if (!strcmp(mux->pdata->pinctrl_states[mux->pdata->bus_count], "idle")) { if (i != num_names - 1) { - dev_err(mux->dev, "idle state must be last\n"); + dev_err(&pdev->dev, + "idle state must be last\n"); return -EINVAL; } mux->pdata->pinctrl_state_idle = "idle"; @@ -107,13 +99,13 @@ static int i2c_mux_pinctrl_parse_dt(struct i2c_mux_pinctrl *mux, adapter_np = of_parse_phandle(np, "i2c-parent", 0); if (!adapter_np) { - dev_err(mux->dev, "Cannot parse i2c-parent\n"); + dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); return -ENODEV; } adapter = of_find_i2c_adapter_by_node(adapter_np); of_node_put(adapter_np); if (!adapter) { - dev_err(mux->dev, "Cannot find parent bus\n"); + dev_err(&pdev->dev, "Cannot find parent bus\n"); return -EPROBE_DEFER; } mux->pdata->parent_bus_num = i2c_adapter_id(adapter); @@ -129,21 +121,38 @@ static inline int i2c_mux_pinctrl_parse_dt(struct i2c_mux_pinctrl *mux, } #endif +static struct i2c_adapter *i2c_mux_pinctrl_root_adapter( + struct pinctrl_state *state) +{ + struct i2c_adapter *root = NULL; + struct pinctrl_setting *setting; + struct i2c_adapter *pin_root; + + list_for_each_entry(setting, &state->settings, node) { + pin_root = i2c_root_adapter(setting->pctldev->dev); + if (!pin_root) + return NULL; + if (!root) + root = pin_root; + else if (root != pin_root) + return NULL; + } + + return root; +} + static int i2c_mux_pinctrl_probe(struct platform_device *pdev) { + struct i2c_mux_core *muxc; struct i2c_mux_pinctrl *mux; - int (*deselect)(struct i2c_adapter *, void *, u32); + struct i2c_adapter *root; int i, ret; mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); if (!mux) { - dev_err(&pdev->dev, "Cannot allocate i2c_mux_pinctrl\n"); ret = -ENOMEM; goto err; } - platform_set_drvdata(pdev, mux); - - mux->dev = &pdev->dev; mux->pdata = dev_get_platdata(&pdev->dev); if (!mux->pdata) { @@ -166,14 +175,15 @@ static int i2c_mux_pinctrl_probe(struct platform_device *pdev) goto err; } - mux->busses = devm_kzalloc(&pdev->dev, - sizeof(*mux->busses) * mux->pdata->bus_count, - GFP_KERNEL); - if (!mux->busses) { - dev_err(&pdev->dev, "Cannot allocate busses\n"); + muxc = i2c_mux_alloc(NULL, &pdev->dev, mux->pdata->bus_count, 0, 0, + i2c_mux_pinctrl_select, NULL); + if (!muxc) { ret = -ENOMEM; goto err; } + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); mux->pinctrl = devm_pinctrl_get(&pdev->dev); if (IS_ERR(mux->pinctrl)) { @@ -184,13 +194,13 @@ static int i2c_mux_pinctrl_probe(struct platform_device *pdev) for (i = 0; i < mux->pdata->bus_count; i++) { mux->states[i] = pinctrl_lookup_state(mux->pinctrl, mux->pdata->pinctrl_states[i]); - if (IS_ERR(mux->states[i])) { - ret = PTR_ERR(mux->states[i]); - dev_err(&pdev->dev, - "Cannot look up pinctrl state %s: %d\n", - mux->pdata->pinctrl_states[i], ret); - goto err; - } + if (IS_ERR(mux->states[i])) { + ret = PTR_ERR(mux->states[i]); + dev_err(&pdev->dev, + "Cannot look up pinctrl state %s: %d\n", + mux->pdata->pinctrl_states[i], ret); + goto err; + } } if (mux->pdata->pinctrl_state_idle) { mux->state_idle = pinctrl_lookup_state(mux->pinctrl, @@ -203,29 +213,39 @@ static int i2c_mux_pinctrl_probe(struct platform_device *pdev) goto err; } - deselect = i2c_mux_pinctrl_deselect; - } else { - deselect = NULL; + muxc->deselect = i2c_mux_pinctrl_deselect; } - mux->parent = i2c_get_adapter(mux->pdata->parent_bus_num); - if (!mux->parent) { + muxc->parent = i2c_get_adapter(mux->pdata->parent_bus_num); + if (!muxc->parent) { dev_err(&pdev->dev, "Parent adapter (%d) not found\n", mux->pdata->parent_bus_num); ret = -EPROBE_DEFER; goto err; } + root = i2c_root_adapter(&muxc->parent->dev); + + muxc->mux_locked = true; + for (i = 0; i < mux->pdata->bus_count; i++) { + if (root != i2c_mux_pinctrl_root_adapter(mux->states[i])) { + muxc->mux_locked = false; + break; + } + } + if (muxc->mux_locked && mux->pdata->pinctrl_state_idle && + root != i2c_mux_pinctrl_root_adapter(mux->state_idle)) + muxc->mux_locked = false; + + if (muxc->mux_locked) + dev_info(&pdev->dev, "mux-locked i2c mux\n"); + for (i = 0; i < mux->pdata->bus_count; i++) { u32 bus = mux->pdata->base_bus_num ? (mux->pdata->base_bus_num + i) : 0; - mux->busses[i] = i2c_add_mux_adapter(mux->parent, &pdev->dev, - mux, bus, i, 0, - i2c_mux_pinctrl_select, - deselect); - if (!mux->busses[i]) { - ret = -ENODEV; + ret = i2c_mux_add_adapter(muxc, bus, i, 0); + if (ret) { dev_err(&pdev->dev, "Failed to add adapter %d\n", i); goto err_del_adapter; } @@ -234,23 +254,18 @@ static int i2c_mux_pinctrl_probe(struct platform_device *pdev) return 0; err_del_adapter: - for (; i > 0; i--) - i2c_del_mux_adapter(mux->busses[i - 1]); - i2c_put_adapter(mux->parent); + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); err: return ret; } static int i2c_mux_pinctrl_remove(struct platform_device *pdev) { - struct i2c_mux_pinctrl *mux = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < mux->pdata->bus_count; i++) - i2c_del_mux_adapter(mux->busses[i]); - - i2c_put_adapter(mux->parent); + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); return 0; } diff --git a/drivers/i2c/muxes/i2c-mux-reg.c b/drivers/i2c/muxes/i2c-mux-reg.c index 49fc2c7..c6a90b4 100644 --- a/drivers/i2c/muxes/i2c-mux-reg.c +++ b/drivers/i2c/muxes/i2c-mux-reg.c @@ -21,8 +21,6 @@ #include struct regmux { - struct i2c_adapter *parent; - struct i2c_adapter **adap; /* child busses */ struct i2c_mux_reg_platform_data data; }; @@ -64,18 +62,16 @@ static int i2c_mux_reg_set(const struct regmux *mux, unsigned int chan_id) return 0; } -static int i2c_mux_reg_select(struct i2c_adapter *adap, void *data, - unsigned int chan) +static int i2c_mux_reg_select(struct i2c_mux_core *muxc, u32 chan) { - struct regmux *mux = data; + struct regmux *mux = i2c_mux_priv(muxc); return i2c_mux_reg_set(mux, chan); } -static int i2c_mux_reg_deselect(struct i2c_adapter *adap, void *data, - unsigned int chan) +static int i2c_mux_reg_deselect(struct i2c_mux_core *muxc, u32 chan) { - struct regmux *mux = data; + struct regmux *mux = i2c_mux_priv(muxc); if (mux->data.idle_in_use) return i2c_mux_reg_set(mux, mux->data.idle); @@ -85,7 +81,7 @@ static int i2c_mux_reg_deselect(struct i2c_adapter *adap, void *data, #ifdef CONFIG_OF static int i2c_mux_reg_probe_dt(struct regmux *mux, - struct platform_device *pdev) + struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device_node *adapter_np, *child; @@ -107,7 +103,6 @@ static int i2c_mux_reg_probe_dt(struct regmux *mux, if (!adapter) return -EPROBE_DEFER; - mux->parent = adapter; mux->data.parent = i2c_adapter_id(adapter); put_device(&adapter->dev); @@ -161,7 +156,7 @@ static int i2c_mux_reg_probe_dt(struct regmux *mux, } #else static int i2c_mux_reg_probe_dt(struct regmux *mux, - struct platform_device *pdev) + struct platform_device *pdev) { return 0; } @@ -169,10 +164,10 @@ static int i2c_mux_reg_probe_dt(struct regmux *mux, static int i2c_mux_reg_probe(struct platform_device *pdev) { + struct i2c_mux_core *muxc; struct regmux *mux; struct i2c_adapter *parent; struct resource *res; - int (*deselect)(struct i2c_adapter *, void *, u32); unsigned int class; int i, ret, nr; @@ -180,17 +175,9 @@ static int i2c_mux_reg_probe(struct platform_device *pdev) if (!mux) return -ENOMEM; - platform_set_drvdata(pdev, mux); - if (dev_get_platdata(&pdev->dev)) { memcpy(&mux->data, dev_get_platdata(&pdev->dev), sizeof(mux->data)); - - parent = i2c_get_adapter(mux->data.parent); - if (!parent) - return -EPROBE_DEFER; - - mux->parent = parent; } else { ret = i2c_mux_reg_probe_dt(mux, pdev); if (ret < 0) { @@ -199,6 +186,10 @@ static int i2c_mux_reg_probe(struct platform_device *pdev) } } + parent = i2c_get_adapter(mux->data.parent); + if (!parent) + return -EPROBE_DEFER; + if (!mux->data.reg) { dev_info(&pdev->dev, "Register not set, using platform resource\n"); @@ -215,55 +206,45 @@ static int i2c_mux_reg_probe(struct platform_device *pdev) return -EINVAL; } - mux->adap = devm_kzalloc(&pdev->dev, - sizeof(*mux->adap) * mux->data.n_values, - GFP_KERNEL); - if (!mux->adap) { - dev_err(&pdev->dev, "Cannot allocate i2c_adapter structure"); + muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 0, 0, + i2c_mux_reg_select, NULL); + if (!muxc) return -ENOMEM; - } + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); if (mux->data.idle_in_use) - deselect = i2c_mux_reg_deselect; - else - deselect = NULL; + muxc->deselect = i2c_mux_reg_deselect; for (i = 0; i < mux->data.n_values; i++) { nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; class = mux->data.classes ? mux->data.classes[i] : 0; - mux->adap[i] = i2c_add_mux_adapter(mux->parent, &pdev->dev, mux, - nr, mux->data.values[i], - class, i2c_mux_reg_select, - deselect); - if (!mux->adap[i]) { - ret = -ENODEV; + ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); + if (ret) { dev_err(&pdev->dev, "Failed to add adapter %d\n", i); goto add_adapter_failed; } } dev_dbg(&pdev->dev, "%d port mux on %s adapter\n", - mux->data.n_values, mux->parent->name); + mux->data.n_values, muxc->parent->name); return 0; add_adapter_failed: - for (; i > 0; i--) - i2c_del_mux_adapter(mux->adap[i - 1]); + i2c_mux_del_adapters(muxc); return ret; } static int i2c_mux_reg_remove(struct platform_device *pdev) { - struct regmux *mux = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < mux->data.n_values; i++) - i2c_del_mux_adapter(mux->adap[i]); + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); - i2c_put_adapter(mux->parent); + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); return 0; } @@ -279,6 +260,7 @@ static struct platform_driver i2c_mux_reg_driver = { .remove = i2c_mux_reg_remove, .driver = { .name = "i2c-mux-reg", + .of_match_table = of_match_ptr(i2c_mux_reg_of_match), }, }; diff --git a/drivers/iio/Kconfig b/drivers/iio/Kconfig index 66792e7..505e921 100644 --- a/drivers/iio/Kconfig +++ b/drivers/iio/Kconfig @@ -22,6 +22,14 @@ if IIO_BUFFER source "drivers/iio/buffer/Kconfig" endif # IIO_BUFFER +config IIO_CONFIGFS + tristate "Enable IIO configuration via configfs" + select CONFIGFS_FS + help + This allows configuring various IIO bits through configfs + (e.g. software triggers). For more info see + Documentation/iio/iio_configfs.txt. + config IIO_TRIGGER bool "Enable triggered sampling support" help @@ -38,6 +46,14 @@ config IIO_CONSUMERS_PER_TRIGGER This value controls the maximum number of consumers that a given trigger may handle. Default is 2. +config IIO_SW_TRIGGER + tristate "Enable software triggers support" + select IIO_CONFIGFS + help + Provides IIO core support for software triggers. A software + trigger can be created via configfs or directly by a driver + using the API provided. + config IIO_TRIGGERED_EVENT tristate select IIO_TRIGGER @@ -50,8 +66,10 @@ source "drivers/iio/amplifiers/Kconfig" source "drivers/iio/chemical/Kconfig" source "drivers/iio/common/Kconfig" source "drivers/iio/dac/Kconfig" +source "drivers/iio/dummy/Kconfig" source "drivers/iio/frequency/Kconfig" source "drivers/iio/gyro/Kconfig" +source "drivers/iio/health/Kconfig" source "drivers/iio/humidity/Kconfig" source "drivers/iio/imu/Kconfig" source "drivers/iio/light/Kconfig" diff --git a/drivers/iio/Makefile b/drivers/iio/Makefile index aeca726..20f6490 100644 --- a/drivers/iio/Makefile +++ b/drivers/iio/Makefile @@ -7,6 +7,8 @@ industrialio-y := industrialio-core.o industrialio-event.o inkern.o industrialio-$(CONFIG_IIO_BUFFER) += industrialio-buffer.o industrialio-$(CONFIG_IIO_TRIGGER) += industrialio-trigger.o +obj-$(CONFIG_IIO_CONFIGFS) += industrialio-configfs.o +obj-$(CONFIG_IIO_SW_TRIGGER) += industrialio-sw-trigger.o obj-$(CONFIG_IIO_TRIGGERED_EVENT) += industrialio-triggered-event.o obj-y += accel/ @@ -16,8 +18,10 @@ obj-y += buffer/ obj-y += chemical/ obj-y += common/ obj-y += dac/ +obj-y += dummy/ obj-y += gyro/ obj-y += frequency/ +obj-y += health/ obj-y += humidity/ obj-y += imu/ obj-y += light/ diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 16cc5c6..e4a758c 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -64,7 +64,7 @@ config IIO_ST_ACCEL_3AXIS help Say yes here to build support for STMicroelectronics accelerometers: LSM303DLH, LSM303DLHC, LIS3DH, LSM330D, LSM330DL, LSM330DLC, - LIS331DLH, LSM303DL, LSM303DLM, LSM330. + LIS331DLH, LSM303DL, LSM303DLM, LSM330, LIS2DH12, H3LIS331DL. This driver can also be built as a module. If so, these modules will be created: @@ -107,6 +107,35 @@ config KXCJK1013 To compile this driver as a module, choose M here: the module will be called kxcjk-1013. +config MMA7455 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config MMA7455_I2C + tristate "Freescale MMA7455L/MMA7456L Accelerometer I2C Driver" + depends on I2C + select MMA7455 + select REGMAP_I2C + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_i2c. + +config MMA7455_SPI + tristate "Freescale MMA7455L/MMA7456L Accelerometer SPI Driver" + depends on SPI_MASTER + select MMA7455 + select REGMAP_SPI + help + Say yes here to build support for the Freescale MMA7455L and + MMA7456L 3-axis accelerometer. + + To compile this driver as a module, choose M here: the module + will be called mma7455_spi. + config MMA8452 tristate "Freescale MMA8452Q and similar Accelerometers Driver" depends on I2C @@ -114,7 +143,8 @@ config MMA8452 select IIO_TRIGGERED_BUFFER help Say yes here to build support for the following Freescale 3-axis - accelerometers: MMA8452Q, MMA8453Q, MMA8652FC, MMA8653FC. + accelerometers: MMA8451Q, MMA8452Q, MMA8453Q, MMA8652FC, MMA8653FC, + FXLS8471Q. To compile this driver as a module, choose M here: the module will be called mma8452. @@ -158,6 +188,17 @@ config MXC4005 To compile this driver as a module, choose M. The module will be called mxc4005. +config MXC6255 + tristate "Memsic MXC6255 Orientation Sensing Accelerometer Driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the Memsic MXC6255 Orientation + Sensing Accelerometer Driver. + + To compile this driver as a module, choose M here: the module will be + called mxc6255. + config STK8312 tristate "Sensortek STK8312 3-Axis Accelerometer Driver" depends on I2C diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 7925f16..71b6794 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -10,6 +10,11 @@ obj-$(CONFIG_BMC150_ACCEL_SPI) += bmc150-accel-spi.o obj-$(CONFIG_HID_SENSOR_ACCEL_3D) += hid-sensor-accel-3d.o obj-$(CONFIG_KXCJK1013) += kxcjk-1013.o obj-$(CONFIG_KXSD9) += kxsd9.o + +obj-$(CONFIG_MMA7455) += mma7455_core.o +obj-$(CONFIG_MMA7455_I2C) += mma7455_i2c.o +obj-$(CONFIG_MMA7455_SPI) += mma7455_spi.o + obj-$(CONFIG_MMA8452) += mma8452.o obj-$(CONFIG_MMA9551_CORE) += mma9551_core.o @@ -17,6 +22,7 @@ obj-$(CONFIG_MMA9551) += mma9551.o obj-$(CONFIG_MMA9553) += mma9553.o obj-$(CONFIG_MXC4005) += mxc4005.o +obj-$(CONFIG_MXC6255) += mxc6255.o obj-$(CONFIG_STK8312) += stk8312.o obj-$(CONFIG_STK8BA50) += stk8ba50.o diff --git a/drivers/iio/accel/bmc150-accel-core.c b/drivers/iio/accel/bmc150-accel-core.c index 291c61a..197e693 100644 --- a/drivers/iio/accel/bmc150-accel-core.c +++ b/drivers/iio/accel/bmc150-accel-core.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -138,6 +137,7 @@ enum bmc150_accel_axis { AXIS_X, AXIS_Y, AXIS_Z, + AXIS_MAX, }; enum bmc150_power_modes { @@ -188,7 +188,6 @@ enum bmc150_accel_trigger_id { struct bmc150_accel_data { struct regmap *regmap; - struct device *dev; int irq; struct bmc150_accel_interrupt interrupts[BMC150_ACCEL_INTERRUPTS]; atomic_t active_intr; @@ -246,16 +245,18 @@ static const struct { {500000, BMC150_ACCEL_SLEEP_500_MS}, {1000000, BMC150_ACCEL_SLEEP_1_SEC} }; -static const struct regmap_config bmc150_i2c_regmap_conf = { +const struct regmap_config bmc150_regmap_conf = { .reg_bits = 8, .val_bits = 8, .max_register = 0x3f, }; +EXPORT_SYMBOL_GPL(bmc150_regmap_conf); static int bmc150_accel_set_mode(struct bmc150_accel_data *data, enum bmc150_power_modes mode, int dur_us) { + struct device *dev = regmap_get_device(data->regmap); int i; int ret; u8 lpw_bits; @@ -279,11 +280,11 @@ static int bmc150_accel_set_mode(struct bmc150_accel_data *data, lpw_bits = mode << BMC150_ACCEL_PMU_MODE_SHIFT; lpw_bits |= (dur_val << BMC150_ACCEL_PMU_BIT_SLEEP_DUR_SHIFT); - dev_dbg(data->dev, "Set Mode bits %x\n", lpw_bits); + dev_dbg(dev, "Set Mode bits %x\n", lpw_bits); ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_LPW, lpw_bits); if (ret < 0) { - dev_err(data->dev, "Error writing reg_pmu_lpw\n"); + dev_err(dev, "Error writing reg_pmu_lpw\n"); return ret; } @@ -316,23 +317,24 @@ static int bmc150_accel_set_bw(struct bmc150_accel_data *data, int val, static int bmc150_accel_update_slope(struct bmc150_accel_data *data) { + struct device *dev = regmap_get_device(data->regmap); int ret; ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_6, data->slope_thres); if (ret < 0) { - dev_err(data->dev, "Error writing reg_int_6\n"); + dev_err(dev, "Error writing reg_int_6\n"); return ret; } ret = regmap_update_bits(data->regmap, BMC150_ACCEL_REG_INT_5, BMC150_ACCEL_SLOPE_DUR_MASK, data->slope_dur); if (ret < 0) { - dev_err(data->dev, "Error updating reg_int_5\n"); + dev_err(dev, "Error updating reg_int_5\n"); return ret; } - dev_dbg(data->dev, "%s: %x %x\n", __func__, data->slope_thres, + dev_dbg(dev, "%s: %x %x\n", __func__, data->slope_thres, data->slope_dur); return ret; @@ -378,20 +380,21 @@ static int bmc150_accel_get_startup_times(struct bmc150_accel_data *data) static int bmc150_accel_set_power_state(struct bmc150_accel_data *data, bool on) { + struct device *dev = regmap_get_device(data->regmap); int ret; if (on) { - ret = pm_runtime_get_sync(data->dev); + ret = pm_runtime_get_sync(dev); } else { - pm_runtime_mark_last_busy(data->dev); - ret = pm_runtime_put_autosuspend(data->dev); + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); } if (ret < 0) { - dev_err(data->dev, + dev_err(dev, "Failed: bmc150_accel_set_power_state for %d\n", on); if (on) - pm_runtime_put_noidle(data->dev); + pm_runtime_put_noidle(dev); return ret; } @@ -445,6 +448,7 @@ static void bmc150_accel_interrupts_setup(struct iio_dev *indio_dev, static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i, bool state) { + struct device *dev = regmap_get_device(data->regmap); struct bmc150_accel_interrupt *intr = &data->interrupts[i]; const struct bmc150_accel_interrupt_info *info = intr->info; int ret; @@ -474,7 +478,7 @@ static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i, ret = regmap_update_bits(data->regmap, info->map_reg, info->map_bitmask, (state ? info->map_bitmask : 0)); if (ret < 0) { - dev_err(data->dev, "Error updating reg_int_map\n"); + dev_err(dev, "Error updating reg_int_map\n"); goto out_fix_power_state; } @@ -482,7 +486,7 @@ static int bmc150_accel_set_interrupt(struct bmc150_accel_data *data, int i, ret = regmap_update_bits(data->regmap, info->en_reg, info->en_bitmask, (state ? info->en_bitmask : 0)); if (ret < 0) { - dev_err(data->dev, "Error updating reg_int_en\n"); + dev_err(dev, "Error updating reg_int_en\n"); goto out_fix_power_state; } @@ -500,6 +504,7 @@ out_fix_power_state: static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val) { + struct device *dev = regmap_get_device(data->regmap); int ret, i; for (i = 0; i < ARRAY_SIZE(data->chip_info->scale_table); ++i) { @@ -508,8 +513,7 @@ static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val) BMC150_ACCEL_REG_PMU_RANGE, data->chip_info->scale_table[i].reg_range); if (ret < 0) { - dev_err(data->dev, - "Error writing pmu_range\n"); + dev_err(dev, "Error writing pmu_range\n"); return ret; } @@ -523,6 +527,7 @@ static int bmc150_accel_set_scale(struct bmc150_accel_data *data, int val) static int bmc150_accel_get_temp(struct bmc150_accel_data *data, int *val) { + struct device *dev = regmap_get_device(data->regmap); int ret; unsigned int value; @@ -530,7 +535,7 @@ static int bmc150_accel_get_temp(struct bmc150_accel_data *data, int *val) ret = regmap_read(data->regmap, BMC150_ACCEL_REG_TEMP, &value); if (ret < 0) { - dev_err(data->dev, "Error reading reg_temp\n"); + dev_err(dev, "Error reading reg_temp\n"); mutex_unlock(&data->mutex); return ret; } @@ -545,6 +550,7 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data, struct iio_chan_spec const *chan, int *val) { + struct device *dev = regmap_get_device(data->regmap); int ret; int axis = chan->scan_index; __le16 raw_val; @@ -559,7 +565,7 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data, ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_AXIS_TO_REG(axis), &raw_val, sizeof(raw_val)); if (ret < 0) { - dev_err(data->dev, "Error reading axis %d\n", axis); + dev_err(dev, "Error reading axis %d\n", axis); bmc150_accel_set_power_state(data, false); mutex_unlock(&data->mutex); return ret; @@ -831,6 +837,7 @@ static int bmc150_accel_set_watermark(struct iio_dev *indio_dev, unsigned val) static int bmc150_accel_fifo_transfer(struct bmc150_accel_data *data, char *buffer, int samples) { + struct device *dev = regmap_get_device(data->regmap); int sample_length = 3 * 2; int ret; int total_length = samples * sample_length; @@ -854,7 +861,8 @@ static int bmc150_accel_fifo_transfer(struct bmc150_accel_data *data, } if (ret) - dev_err(data->dev, "Error transferring data from fifo in single steps of %zu\n", + dev_err(dev, + "Error transferring data from fifo in single steps of %zu\n", step); return ret; @@ -864,6 +872,7 @@ static int __bmc150_accel_fifo_flush(struct iio_dev *indio_dev, unsigned samples, bool irq) { struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); int ret, i; u8 count; u16 buffer[BMC150_ACCEL_FIFO_LENGTH * 3]; @@ -873,7 +882,7 @@ static int __bmc150_accel_fifo_flush(struct iio_dev *indio_dev, ret = regmap_read(data->regmap, BMC150_ACCEL_REG_FIFO_STATUS, &val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_fifo_status\n"); + dev_err(dev, "Error reading reg_fifo_status\n"); return ret; } @@ -1105,27 +1114,23 @@ static const struct iio_info bmc150_accel_info_fifo = { .driver_module = THIS_MODULE, }; +static const unsigned long bmc150_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0}; + static irqreturn_t bmc150_accel_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct bmc150_accel_data *data = iio_priv(indio_dev); - int bit, ret, i = 0; - unsigned int raw_val; + int ret; mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->active_scan_mask, - indio_dev->masklength) { - ret = regmap_bulk_read(data->regmap, - BMC150_ACCEL_AXIS_TO_REG(bit), &raw_val, - 2); - if (ret < 0) { - mutex_unlock(&data->mutex); - goto err_read; - } - data->buffer[i++] = raw_val; - } + ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_REG_XOUT_L, + data->buffer, AXIS_MAX * 2); mutex_unlock(&data->mutex); + if (ret < 0) + goto err_read; iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, pf->timestamp); @@ -1139,6 +1144,7 @@ static int bmc150_accel_trig_try_reen(struct iio_trigger *trig) { struct bmc150_accel_trigger *t = iio_trigger_get_drvdata(trig); struct bmc150_accel_data *data = t->data; + struct device *dev = regmap_get_device(data->regmap); int ret; /* new data interrupts don't need ack */ @@ -1152,8 +1158,7 @@ static int bmc150_accel_trig_try_reen(struct iio_trigger *trig) BMC150_ACCEL_INT_MODE_LATCH_RESET); mutex_unlock(&data->mutex); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_int_rst_latch\n"); + dev_err(dev, "Error writing reg_int_rst_latch\n"); return ret; } @@ -1204,13 +1209,14 @@ static const struct iio_trigger_ops bmc150_accel_trigger_ops = { static int bmc150_accel_handle_roc_event(struct iio_dev *indio_dev) { struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); int dir; int ret; unsigned int val; ret = regmap_read(data->regmap, BMC150_ACCEL_REG_INT_STATUS_2, &val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_int_status_2\n"); + dev_err(dev, "Error reading reg_int_status_2\n"); return ret; } @@ -1253,6 +1259,7 @@ static irqreturn_t bmc150_accel_irq_thread_handler(int irq, void *private) { struct iio_dev *indio_dev = private; struct bmc150_accel_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); bool ack = false; int ret; @@ -1276,7 +1283,7 @@ static irqreturn_t bmc150_accel_irq_thread_handler(int irq, void *private) BMC150_ACCEL_INT_MODE_LATCH_INT | BMC150_ACCEL_INT_MODE_LATCH_RESET); if (ret) - dev_err(data->dev, "Error writing reg_int_rst_latch\n"); + dev_err(dev, "Error writing reg_int_rst_latch\n"); ret = IRQ_HANDLED; } else { @@ -1347,13 +1354,14 @@ static void bmc150_accel_unregister_triggers(struct bmc150_accel_data *data, static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev, struct bmc150_accel_data *data) { + struct device *dev = regmap_get_device(data->regmap); int i, ret; for (i = 0; i < BMC150_ACCEL_TRIGGERS; i++) { struct bmc150_accel_trigger *t = &data->triggers[i]; - t->indio_trig = devm_iio_trigger_alloc(data->dev, - bmc150_accel_triggers[i].name, + t->indio_trig = devm_iio_trigger_alloc(dev, + bmc150_accel_triggers[i].name, indio_dev->name, indio_dev->id); if (!t->indio_trig) { @@ -1361,7 +1369,7 @@ static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev, break; } - t->indio_trig->dev.parent = data->dev; + t->indio_trig->dev.parent = dev; t->indio_trig->ops = &bmc150_accel_trigger_ops; t->intr = bmc150_accel_triggers[i].intr; t->data = data; @@ -1385,12 +1393,13 @@ static int bmc150_accel_triggers_setup(struct iio_dev *indio_dev, static int bmc150_accel_fifo_set_mode(struct bmc150_accel_data *data) { + struct device *dev = regmap_get_device(data->regmap); u8 reg = BMC150_ACCEL_REG_FIFO_CONFIG1; int ret; ret = regmap_write(data->regmap, reg, data->fifo_mode); if (ret < 0) { - dev_err(data->dev, "Error writing reg_fifo_config1\n"); + dev_err(dev, "Error writing reg_fifo_config1\n"); return ret; } @@ -1400,7 +1409,7 @@ static int bmc150_accel_fifo_set_mode(struct bmc150_accel_data *data) ret = regmap_write(data->regmap, BMC150_ACCEL_REG_FIFO_CONFIG0, data->watermark); if (ret < 0) - dev_err(data->dev, "Error writing reg_fifo_config0\n"); + dev_err(dev, "Error writing reg_fifo_config0\n"); return ret; } @@ -1484,17 +1493,17 @@ static const struct iio_buffer_setup_ops bmc150_accel_buffer_ops = { static int bmc150_accel_chip_init(struct bmc150_accel_data *data) { + struct device *dev = regmap_get_device(data->regmap); int ret, i; unsigned int val; ret = regmap_read(data->regmap, BMC150_ACCEL_REG_CHIP_ID, &val); if (ret < 0) { - dev_err(data->dev, - "Error: Reading chip id\n"); + dev_err(dev, "Error: Reading chip id\n"); return ret; } - dev_dbg(data->dev, "Chip Id %x\n", val); + dev_dbg(dev, "Chip Id %x\n", val); for (i = 0; i < ARRAY_SIZE(bmc150_accel_chip_info_tbl); i++) { if (bmc150_accel_chip_info_tbl[i].chip_id == val) { data->chip_info = &bmc150_accel_chip_info_tbl[i]; @@ -1503,7 +1512,7 @@ static int bmc150_accel_chip_init(struct bmc150_accel_data *data) } if (!data->chip_info) { - dev_err(data->dev, "Invalid chip %x\n", val); + dev_err(dev, "Invalid chip %x\n", val); return -ENODEV; } @@ -1520,8 +1529,7 @@ static int bmc150_accel_chip_init(struct bmc150_accel_data *data) ret = regmap_write(data->regmap, BMC150_ACCEL_REG_PMU_RANGE, BMC150_ACCEL_DEF_RANGE_4G); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_pmu_range\n"); + dev_err(dev, "Error writing reg_pmu_range\n"); return ret; } @@ -1539,8 +1547,7 @@ static int bmc150_accel_chip_init(struct bmc150_accel_data *data) BMC150_ACCEL_INT_MODE_LATCH_INT | BMC150_ACCEL_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_int_rst_latch\n"); + dev_err(dev, "Error writing reg_int_rst_latch\n"); return ret; } @@ -1560,7 +1567,6 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, data = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); - data->dev = dev; data->irq = irq; data->regmap = regmap; @@ -1575,6 +1581,7 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, indio_dev->channels = data->chip_info->channels; indio_dev->num_channels = data->chip_info->num_channels; indio_dev->name = name ? name : data->chip_info->name; + indio_dev->available_scan_masks = bmc150_accel_scan_masks; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &bmc150_accel_info; @@ -1583,13 +1590,13 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, bmc150_accel_trigger_handler, &bmc150_accel_buffer_ops); if (ret < 0) { - dev_err(data->dev, "Failed: iio triggered buffer setup\n"); + dev_err(dev, "Failed: iio triggered buffer setup\n"); return ret; } if (data->irq > 0) { ret = devm_request_threaded_irq( - data->dev, data->irq, + dev, data->irq, bmc150_accel_irq_handler, bmc150_accel_irq_thread_handler, IRQF_TRIGGER_RISING, @@ -1607,7 +1614,7 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, ret = regmap_write(data->regmap, BMC150_ACCEL_REG_INT_RST_LATCH, BMC150_ACCEL_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, "Error writing reg_int_rst_latch\n"); + dev_err(dev, "Error writing reg_int_rst_latch\n"); goto err_buffer_cleanup; } @@ -1624,24 +1631,22 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, } } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(dev, "Unable to register iio device\n"); - goto err_trigger_unregister; - } - ret = pm_runtime_set_active(dev); if (ret) - goto err_iio_unregister; + goto err_trigger_unregister; pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, BMC150_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "Unable to register iio device\n"); + goto err_trigger_unregister; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_trigger_unregister: bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); err_buffer_cleanup: @@ -1656,12 +1661,12 @@ int bmc150_accel_core_remove(struct device *dev) struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_accel_data *data = iio_priv(indio_dev); - pm_runtime_disable(data->dev); - pm_runtime_set_suspended(data->dev); - pm_runtime_put_noidle(data->dev); - iio_device_unregister(indio_dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + bmc150_accel_unregister_triggers(data, BMC150_ACCEL_TRIGGERS - 1); iio_triggered_buffer_cleanup(indio_dev); @@ -1709,7 +1714,7 @@ static int bmc150_accel_runtime_suspend(struct device *dev) struct bmc150_accel_data *data = iio_priv(indio_dev); int ret; - dev_dbg(data->dev, __func__); + dev_dbg(dev, __func__); ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_SUSPEND, 0); if (ret < 0) return -EAGAIN; @@ -1724,7 +1729,7 @@ static int bmc150_accel_runtime_resume(struct device *dev) int ret; int sleep_val; - dev_dbg(data->dev, __func__); + dev_dbg(dev, __func__); ret = bmc150_accel_set_mode(data, BMC150_ACCEL_SLEEP_MODE_NORMAL, 0); if (ret < 0) diff --git a/drivers/iio/accel/bmc150-accel-i2c.c b/drivers/iio/accel/bmc150-accel-i2c.c index b41404b..8ca8041 100644 --- a/drivers/iio/accel/bmc150-accel-i2c.c +++ b/drivers/iio/accel/bmc150-accel-i2c.c @@ -28,11 +28,6 @@ #include "bmc150-accel.h" -static const struct regmap_config bmc150_i2c_regmap_conf = { - .reg_bits = 8, - .val_bits = 8, -}; - static int bmc150_accel_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -43,7 +38,7 @@ static int bmc150_accel_probe(struct i2c_client *client, i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK); - regmap = devm_regmap_init_i2c(client, &bmc150_i2c_regmap_conf); + regmap = devm_regmap_init_i2c(client, &bmc150_regmap_conf); if (IS_ERR(regmap)) { dev_err(&client->dev, "Failed to initialize i2c regmap\n"); return PTR_ERR(regmap); diff --git a/drivers/iio/accel/bmc150-accel-spi.c b/drivers/iio/accel/bmc150-accel-spi.c index 16b66f2..006794a 100644 --- a/drivers/iio/accel/bmc150-accel-spi.c +++ b/drivers/iio/accel/bmc150-accel-spi.c @@ -25,18 +25,12 @@ #include "bmc150-accel.h" -static const struct regmap_config bmc150_spi_regmap_conf = { - .reg_bits = 8, - .val_bits = 8, - .max_register = 0x3f, -}; - static int bmc150_accel_probe(struct spi_device *spi) { struct regmap *regmap; const struct spi_device_id *id = spi_get_device_id(spi); - regmap = devm_regmap_init_spi(spi, &bmc150_spi_regmap_conf); + regmap = devm_regmap_init_spi(spi, &bmc150_regmap_conf); if (IS_ERR(regmap)) { dev_err(&spi->dev, "Failed to initialize spi regmap\n"); return PTR_ERR(regmap); diff --git a/drivers/iio/accel/bmc150-accel.h b/drivers/iio/accel/bmc150-accel.h index ba03359..38a8b11 100644 --- a/drivers/iio/accel/bmc150-accel.h +++ b/drivers/iio/accel/bmc150-accel.h @@ -16,5 +16,6 @@ int bmc150_accel_core_probe(struct device *dev, struct regmap *regmap, int irq, const char *name, bool block_supported); int bmc150_accel_core_remove(struct device *dev); extern const struct dev_pm_ops bmc150_accel_pm_ops; +extern const struct regmap_config bmc150_regmap_conf; #endif /* _BMC150_ACCEL_H_ */ diff --git a/drivers/iio/accel/kxcjk-1013.c b/drivers/iio/accel/kxcjk-1013.c index 18c1b06..bfe219a 100644 --- a/drivers/iio/accel/kxcjk-1013.c +++ b/drivers/iio/accel/kxcjk-1013.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -115,6 +114,7 @@ enum kxcjk1013_axis { AXIS_X, AXIS_Y, AXIS_Z, + AXIS_MAX, }; enum kxcjk1013_mode { @@ -922,7 +922,7 @@ static const struct iio_event_spec kxcjk1013_event = { .realbits = 12, \ .storagebits = 16, \ .shift = 4, \ - .endianness = IIO_CPU, \ + .endianness = IIO_LE, \ }, \ .event_spec = &kxcjk1013_event, \ .num_event_specs = 1 \ @@ -953,25 +953,23 @@ static const struct iio_info kxcjk1013_info = { .driver_module = THIS_MODULE, }; +static const unsigned long kxcjk1013_scan_masks[] = {0x7, 0}; + static irqreturn_t kxcjk1013_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct kxcjk1013_data *data = iio_priv(indio_dev); - int bit, ret, i = 0; + int ret; mutex_lock(&data->mutex); - - for_each_set_bit(bit, indio_dev->active_scan_mask, - indio_dev->masklength) { - ret = kxcjk1013_get_acc_reg(data, bit); - if (ret < 0) { - mutex_unlock(&data->mutex); - goto err; - } - data->buffer[i++] = ret; - } + ret = i2c_smbus_read_i2c_block_data_or_emulated(data->client, + KXCJK1013_REG_XOUT_L, + AXIS_MAX * 2, + (u8 *)data->buffer); mutex_unlock(&data->mutex); + if (ret < 0) + goto err; iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, data->timestamp); @@ -1204,6 +1202,7 @@ static int kxcjk1013_probe(struct i2c_client *client, indio_dev->dev.parent = &client->dev; indio_dev->channels = kxcjk1013_channels; indio_dev->num_channels = ARRAY_SIZE(kxcjk1013_channels); + indio_dev->available_scan_masks = kxcjk1013_scan_masks; indio_dev->name = name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &kxcjk1013_info; @@ -1264,25 +1263,23 @@ static int kxcjk1013_probe(struct i2c_client *client, goto err_trigger_unregister; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - ret = pm_runtime_set_active(&client->dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, KXCJK1013_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: if (data->dready_trig) iio_triggered_buffer_cleanup(indio_dev); @@ -1302,12 +1299,12 @@ static int kxcjk1013_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct kxcjk1013_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); - if (data->dready_trig) { iio_triggered_buffer_cleanup(indio_dev); iio_trigger_unregister(data->dready_trig); diff --git b/drivers/iio/accel/mma7455.h b/drivers/iio/accel/mma7455.h new file mode 100644 index 0000000..2b1152c --- /dev/null +++ b/drivers/iio/accel/mma7455.h @@ -0,0 +1,19 @@ +/* + * IIO accel driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + * + * 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. + */ + +#ifndef __MMA7455_H +#define __MMA7455_H + +extern const struct regmap_config mma7455_core_regmap; + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name); +int mma7455_core_remove(struct device *dev); + +#endif diff --git b/drivers/iio/accel/mma7455_core.c b/drivers/iio/accel/mma7455_core.c new file mode 100644 index 0000000..c902f54 --- /dev/null +++ b/drivers/iio/accel/mma7455_core.c @@ -0,0 +1,310 @@ +/* + * IIO accel core driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + * + * 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. + * + * UNSUPPORTED hardware features: + * - 8-bit mode with different scales + * - INT1/INT2 interrupts + * - Offset calibration + * - Events + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mma7455.h" + +#define MMA7455_REG_XOUTL 0x00 +#define MMA7455_REG_XOUTH 0x01 +#define MMA7455_REG_YOUTL 0x02 +#define MMA7455_REG_YOUTH 0x03 +#define MMA7455_REG_ZOUTL 0x04 +#define MMA7455_REG_ZOUTH 0x05 +#define MMA7455_REG_STATUS 0x09 +#define MMA7455_STATUS_DRDY BIT(0) +#define MMA7455_REG_WHOAMI 0x0f +#define MMA7455_WHOAMI_ID 0x55 +#define MMA7455_REG_MCTL 0x16 +#define MMA7455_MCTL_MODE_STANDBY 0x00 +#define MMA7455_MCTL_MODE_MEASURE 0x01 +#define MMA7455_REG_CTL1 0x18 +#define MMA7455_CTL1_DFBW_MASK BIT(7) +#define MMA7455_CTL1_DFBW_125HZ BIT(7) +#define MMA7455_CTL1_DFBW_62_5HZ 0 +#define MMA7455_REG_TW 0x1e + +/* + * When MMA7455 is used in 10-bit it has a fullscale of -8g + * corresponding to raw value -512. The userspace interface + * uses m/s^2 and we declare micro units. + * So scale factor is given by: + * g * 8 * 1e6 / 512 = 153228.90625, with g = 9.80665 + */ +#define MMA7455_10BIT_SCALE 153229 + +struct mma7455_data { + struct regmap *regmap; +}; + +static int mma7455_drdy(struct mma7455_data *mma7455) +{ + struct device *dev = regmap_get_device(mma7455->regmap); + unsigned int reg; + int tries = 3; + int ret; + + while (tries-- > 0) { + ret = regmap_read(mma7455->regmap, MMA7455_REG_STATUS, ®); + if (ret) + return ret; + + if (reg & MMA7455_STATUS_DRDY) + return 0; + + msleep(20); + } + + dev_warn(dev, "data not ready\n"); + + return -EIO; +} + +static irqreturn_t mma7455_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct mma7455_data *mma7455 = iio_priv(indio_dev); + u8 buf[16]; /* 3 x 16-bit channels + padding + ts */ + int ret; + + ret = mma7455_drdy(mma7455); + if (ret) + goto done; + + ret = regmap_bulk_read(mma7455->regmap, MMA7455_REG_XOUTL, buf, + sizeof(__le16) * 3); + if (ret) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int mma7455_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + unsigned int reg; + __le16 data; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + ret = mma7455_drdy(mma7455); + if (ret) + return ret; + + ret = regmap_bulk_read(mma7455->regmap, chan->address, &data, + sizeof(data)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpu(data), 9); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MMA7455_10BIT_SCALE; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(mma7455->regmap, MMA7455_REG_CTL1, ®); + if (ret) + return ret; + + if (reg & MMA7455_CTL1_DFBW_MASK) + *val = 250; + else + *val = 125; + + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int mma7455_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct mma7455_data *mma7455 = iio_priv(indio_dev); + int i; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + if (val == 250 && val2 == 0) + i = MMA7455_CTL1_DFBW_125HZ; + else if (val == 125 && val2 == 0) + i = MMA7455_CTL1_DFBW_62_5HZ; + else + return -EINVAL; + + return regmap_update_bits(mma7455->regmap, MMA7455_REG_CTL1, + MMA7455_CTL1_DFBW_MASK, i); + + case IIO_CHAN_INFO_SCALE: + /* In 10-bit mode there is only one scale available */ + if (val == 0 && val2 == MMA7455_10BIT_SCALE) + return 0; + break; + } + + return -EINVAL; +} + +static IIO_CONST_ATTR(sampling_frequency_available, "125 250"); + +static struct attribute *mma7455_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mma7455_group = { + .attrs = mma7455_attributes, +}; + +static const struct iio_info mma7455_info = { + .attrs = &mma7455_group, + .read_raw = mma7455_read_raw, + .write_raw = mma7455_write_raw, + .driver_module = THIS_MODULE, +}; + +#define MMA7455_CHANNEL(axis, idx) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .address = MMA7455_REG_##axis##OUTL,\ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 10, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +static const struct iio_chan_spec mma7455_channels[] = { + MMA7455_CHANNEL(X, 0), + MMA7455_CHANNEL(Y, 1), + MMA7455_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const unsigned long mma7455_scan_masks[] = {0x7, 0}; + +const struct regmap_config mma7455_core_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MMA7455_REG_TW, +}; +EXPORT_SYMBOL_GPL(mma7455_core_regmap); + +int mma7455_core_probe(struct device *dev, struct regmap *regmap, + const char *name) +{ + struct mma7455_data *mma7455; + struct iio_dev *indio_dev; + unsigned int reg; + int ret; + + ret = regmap_read(regmap, MMA7455_REG_WHOAMI, ®); + if (ret) { + dev_err(dev, "unable to read reg\n"); + return ret; + } + + if (reg != MMA7455_WHOAMI_ID) { + dev_err(dev, "device id mismatch\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(dev, sizeof(*mma7455)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + mma7455 = iio_priv(indio_dev); + mma7455->regmap = regmap; + + indio_dev->info = &mma7455_info; + indio_dev->name = name; + indio_dev->dev.parent = dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = mma7455_channels; + indio_dev->num_channels = ARRAY_SIZE(mma7455_channels); + indio_dev->available_scan_masks = mma7455_scan_masks; + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_MEASURE); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + mma7455_trigger_handler, NULL); + if (ret) { + dev_err(dev, "unable to setup triggered buffer\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "unable to register device\n"); + iio_triggered_buffer_cleanup(indio_dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mma7455_core_probe); + +int mma7455_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct mma7455_data *mma7455 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + regmap_write(mma7455->regmap, MMA7455_REG_MCTL, + MMA7455_MCTL_MODE_STANDBY); + + return 0; +} +EXPORT_SYMBOL_GPL(mma7455_core_remove); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L core accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/accel/mma7455_i2c.c b/drivers/iio/accel/mma7455_i2c.c new file mode 100644 index 0000000..3cab5fb --- /dev/null +++ b/drivers/iio/accel/mma7455_i2c.c @@ -0,0 +1,56 @@ +/* + * IIO accel I2C driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + * + * 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. + */ + +#include +#include +#include + +#include "mma7455.h" + +static int mma7455_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(i2c, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + if (id) + name = id->name; + + return mma7455_core_probe(&i2c->dev, regmap, name); +} + +static int mma7455_i2c_remove(struct i2c_client *i2c) +{ + return mma7455_core_remove(&i2c->dev); +} + +static const struct i2c_device_id mma7455_i2c_ids[] = { + { "mma7455", 0 }, + { "mma7456", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mma7455_i2c_ids); + +static struct i2c_driver mma7455_i2c_driver = { + .probe = mma7455_i2c_probe, + .remove = mma7455_i2c_remove, + .id_table = mma7455_i2c_ids, + .driver = { + .name = "mma7455-i2c", + }, +}; +module_i2c_driver(mma7455_i2c_driver); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L I2C accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/accel/mma7455_spi.c b/drivers/iio/accel/mma7455_spi.c new file mode 100644 index 0000000..79df8f2 --- /dev/null +++ b/drivers/iio/accel/mma7455_spi.c @@ -0,0 +1,52 @@ +/* + * IIO accel SPI driver for Freescale MMA7455L 3-axis 10-bit accelerometer + * Copyright 2015 Joachim Eastwood + * + * 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. + */ + +#include +#include +#include + +#include "mma7455.h" + +static int mma7455_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &mma7455_core_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return mma7455_core_probe(&spi->dev, regmap, id->name); +} + +static int mma7455_spi_remove(struct spi_device *spi) +{ + return mma7455_core_remove(&spi->dev); +} + +static const struct spi_device_id mma7455_spi_ids[] = { + { "mma7455", 0 }, + { "mma7456", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, mma7455_spi_ids); + +static struct spi_driver mma7455_spi_driver = { + .probe = mma7455_spi_probe, + .remove = mma7455_spi_remove, + .id_table = mma7455_spi_ids, + .driver = { + .name = "mma7455-spi", + }, +}; +module_spi_driver(mma7455_spi_driver); + +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_DESCRIPTION("Freescale MMA7455L SPI accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/mma8452.c b/drivers/iio/accel/mma8452.c index 1eccc2d..e225d3c 100644 --- a/drivers/iio/accel/mma8452.c +++ b/drivers/iio/accel/mma8452.c @@ -1,10 +1,12 @@ /* * mma8452.c - Support for following Freescale 3-axis accelerometers: * + * MMA8451Q (14 bit) * MMA8452Q (12 bit) * MMA8453Q (10 bit) * MMA8652FC (12 bit) * MMA8653FC (10 bit) + * FXLS8471Q (14 bit) * * Copyright 2015 Martin Kepplinger * Copyright 2014 Peter Meerwald @@ -15,7 +17,7 @@ * * 7-bit I2C slave address 0x1c/0x1d (pin selectable) * - * TODO: orientation / freefall events, autosleep + * TODO: orientation events */ #include @@ -29,6 +31,8 @@ #include #include #include +#include +#include #define MMA8452_STATUS 0x00 #define MMA8452_STATUS_DRDY (BIT(2) | BIT(1) | BIT(0)) @@ -57,7 +61,6 @@ #define MMA8452_FF_MT_COUNT 0x18 #define MMA8452_TRANSIENT_CFG 0x1d #define MMA8452_TRANSIENT_CFG_HPF_BYP BIT(0) -#define MMA8452_TRANSIENT_CFG_CHAN(chan) BIT(chan + 1) #define MMA8452_TRANSIENT_CFG_ELE BIT(4) #define MMA8452_TRANSIENT_SRC 0x1e #define MMA8452_TRANSIENT_SRC_XTRANSE BIT(1) @@ -85,10 +88,14 @@ #define MMA8452_INT_FF_MT BIT(2) #define MMA8452_INT_TRANS BIT(5) -#define MMA8452_DEVICE_ID 0x2a -#define MMA8453_DEVICE_ID 0x3a +#define MMA8451_DEVICE_ID 0x1a +#define MMA8452_DEVICE_ID 0x2a +#define MMA8453_DEVICE_ID 0x3a #define MMA8652_DEVICE_ID 0x4a #define MMA8653_DEVICE_ID 0x5a +#define FXLS8471_DEVICE_ID 0x6a + +#define MMA8452_AUTO_SUSPEND_DELAY_MS 2000 struct mma8452_data { struct i2c_client *client; @@ -143,6 +150,13 @@ struct mma_chip_info { u8 ev_count; }; +enum { + idx_x, + idx_y, + idx_z, + idx_ts, +}; + static int mma8452_drdy(struct mma8452_data *data) { int tries = 150; @@ -163,6 +177,31 @@ static int mma8452_drdy(struct mma8452_data *data) return -EIO; } +static int mma8452_set_runtime_pm_state(struct i2c_client *client, bool on) +{ +#ifdef CONFIG_PM + int ret; + + if (on) { + ret = pm_runtime_get_sync(&client->dev); + } else { + pm_runtime_mark_last_busy(&client->dev); + ret = pm_runtime_put_autosuspend(&client->dev); + } + + if (ret < 0) { + dev_err(&client->dev, + "failed to change power state to %d\n", on); + if (on) + pm_runtime_put_noidle(&client->dev); + + return ret; + } +#endif + + return 0; +} + static int mma8452_read(struct mma8452_data *data, __be16 buf[3]) { int ret = mma8452_drdy(data); @@ -170,8 +209,16 @@ static int mma8452_read(struct mma8452_data *data, __be16 buf[3]) if (ret < 0) return ret; - return i2c_smbus_read_i2c_block_data(data->client, MMA8452_OUT_X, - 3 * sizeof(__be16), (u8 *)buf); + ret = mma8452_set_runtime_pm_state(data->client, true); + if (ret) + return ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, MMA8452_OUT_X, + 3 * sizeof(__be16), (u8 *)buf); + + ret = mma8452_set_runtime_pm_state(data->client, false); + + return ret; } static ssize_t mma8452_show_int_plus_micros(char *buf, const int (*vals)[2], @@ -348,7 +395,8 @@ static int mma8452_read_raw(struct iio_dev *indio_dev, return IIO_VAL_INT_PLUS_MICRO; case IIO_CHAN_INFO_CALIBBIAS: ret = i2c_smbus_read_byte_data(data->client, - MMA8452_OFF_X + chan->scan_index); + MMA8452_OFF_X + + chan->scan_index); if (ret < 0) return ret; @@ -383,24 +431,47 @@ static int mma8452_active(struct mma8452_data *data) data->ctrl_reg1); } +/* returns >0 if active, 0 if in standby and <0 on error */ +static int mma8452_is_active(struct mma8452_data *data) +{ + int reg; + + reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG1); + if (reg < 0) + return reg; + + return reg & MMA8452_CTRL_ACTIVE; +} + static int mma8452_change_config(struct mma8452_data *data, u8 reg, u8 val) { int ret; + int is_active; mutex_lock(&data->lock); - /* config can only be changed when in standby */ - ret = mma8452_standby(data); - if (ret < 0) + is_active = mma8452_is_active(data); + if (is_active < 0) { + ret = is_active; goto fail; + } + + /* config can only be changed when in standby */ + if (is_active > 0) { + ret = mma8452_standby(data); + if (ret < 0) + goto fail; + } ret = i2c_smbus_write_byte_data(data->client, reg, val); if (ret < 0) goto fail; - ret = mma8452_active(data); - if (ret < 0) - goto fail; + if (is_active > 0) { + ret = mma8452_active(data); + if (ret < 0) + goto fail; + } ret = 0; fail: @@ -409,6 +480,51 @@ fail: return ret; } +/* returns >0 if in freefall mode, 0 if not or <0 if an error occurred */ +static int mma8452_freefall_mode_enabled(struct mma8452_data *data) +{ + int val; + const struct mma_chip_info *chip = data->chip_info; + + val = i2c_smbus_read_byte_data(data->client, chip->ev_cfg); + if (val < 0) + return val; + + return !(val & MMA8452_FF_MT_CFG_OAE); +} + +static int mma8452_set_freefall_mode(struct mma8452_data *data, bool state) +{ + int val; + const struct mma_chip_info *chip = data->chip_info; + + if ((state && mma8452_freefall_mode_enabled(data)) || + (!state && !(mma8452_freefall_mode_enabled(data)))) + return 0; + + val = i2c_smbus_read_byte_data(data->client, chip->ev_cfg); + if (val < 0) + return val; + + if (state) { + val |= BIT(idx_x + chip->ev_cfg_chan_shift); + val |= BIT(idx_y + chip->ev_cfg_chan_shift); + val |= BIT(idx_z + chip->ev_cfg_chan_shift); + val &= ~MMA8452_FF_MT_CFG_OAE; + } else { + val &= ~BIT(idx_x + chip->ev_cfg_chan_shift); + val &= ~BIT(idx_y + chip->ev_cfg_chan_shift); + val &= ~BIT(idx_z + chip->ev_cfg_chan_shift); + val |= MMA8452_FF_MT_CFG_OAE; + } + + val = mma8452_change_config(data, chip->ev_cfg, val); + if (val) + return val; + + return 0; +} + static int mma8452_set_hp_filter_frequency(struct mma8452_data *data, int val, int val2) { @@ -602,12 +718,23 @@ static int mma8452_read_event_config(struct iio_dev *indio_dev, const struct mma_chip_info *chip = data->chip_info; int ret; - ret = i2c_smbus_read_byte_data(data->client, - data->chip_info->ev_cfg); - if (ret < 0) - return ret; + switch (dir) { + case IIO_EV_DIR_FALLING: + return mma8452_freefall_mode_enabled(data); + case IIO_EV_DIR_RISING: + if (mma8452_freefall_mode_enabled(data)) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, + data->chip_info->ev_cfg); + if (ret < 0) + return ret; - return !!(ret & BIT(chan->scan_index + chip->ev_cfg_chan_shift)); + return !!(ret & BIT(chan->scan_index + + chip->ev_cfg_chan_shift)); + default: + return -EINVAL; + } } static int mma8452_write_event_config(struct iio_dev *indio_dev, @@ -618,21 +745,41 @@ static int mma8452_write_event_config(struct iio_dev *indio_dev, { struct mma8452_data *data = iio_priv(indio_dev); const struct mma_chip_info *chip = data->chip_info; - int val; + int val, ret; - val = i2c_smbus_read_byte_data(data->client, chip->ev_cfg); - if (val < 0) - return val; + ret = mma8452_set_runtime_pm_state(data->client, state); + if (ret) + return ret; - if (state) - val |= BIT(chan->scan_index + chip->ev_cfg_chan_shift); - else - val &= ~BIT(chan->scan_index + chip->ev_cfg_chan_shift); + switch (dir) { + case IIO_EV_DIR_FALLING: + return mma8452_set_freefall_mode(data, state); + case IIO_EV_DIR_RISING: + val = i2c_smbus_read_byte_data(data->client, chip->ev_cfg); + if (val < 0) + return val; + + if (state) { + if (mma8452_freefall_mode_enabled(data)) { + val &= ~BIT(idx_x + chip->ev_cfg_chan_shift); + val &= ~BIT(idx_y + chip->ev_cfg_chan_shift); + val &= ~BIT(idx_z + chip->ev_cfg_chan_shift); + val |= MMA8452_FF_MT_CFG_OAE; + } + val |= BIT(chan->scan_index + chip->ev_cfg_chan_shift); + } else { + if (mma8452_freefall_mode_enabled(data)) + return 0; + + val &= ~BIT(chan->scan_index + chip->ev_cfg_chan_shift); + } - val |= chip->ev_cfg_ele; - val |= MMA8452_FF_MT_CFG_OAE; + val |= chip->ev_cfg_ele; - return mma8452_change_config(data, chip->ev_cfg, val); + return mma8452_change_config(data, chip->ev_cfg, val); + default: + return -EINVAL; + } } static void mma8452_transient_interrupt(struct iio_dev *indio_dev) @@ -645,6 +792,16 @@ static void mma8452_transient_interrupt(struct iio_dev *indio_dev) if (src < 0) return; + if (mma8452_freefall_mode_enabled(data)) { + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + ts); + return; + } + if (src & data->chip_info->ev_src_xe) iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, @@ -738,6 +895,27 @@ static int mma8452_reg_access_dbg(struct iio_dev *indio_dev, return 0; } +static const struct iio_event_spec mma8452_freefall_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HIGH_PASS_FILTER_3DB) + }, +}; + +static const struct iio_event_spec mma8652_freefall_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) + }, +}; + static const struct iio_event_spec mma8452_transient_event[] = { { .type = IIO_EV_TYPE_MAG, @@ -774,6 +952,24 @@ static struct attribute_group mma8452_event_attribute_group = { .attrs = mma8452_event_attributes, }; +#define MMA8452_FREEFALL_CHANNEL(modifier) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = modifier, \ + .scan_index = -1, \ + .event_spec = mma8452_freefall_event, \ + .num_event_specs = ARRAY_SIZE(mma8452_freefall_event), \ +} + +#define MMA8652_FREEFALL_CHANNEL(modifier) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = modifier, \ + .scan_index = -1, \ + .event_spec = mma8652_freefall_event, \ + .num_event_specs = ARRAY_SIZE(mma8652_freefall_event), \ +} + #define MMA8452_CHANNEL(axis, idx, bits) { \ .type = IIO_ACCEL, \ .modified = 1, \ @@ -815,53 +1011,84 @@ static struct attribute_group mma8452_event_attribute_group = { .num_event_specs = ARRAY_SIZE(mma8452_motion_event), \ } +static const struct iio_chan_spec mma8451_channels[] = { + MMA8452_CHANNEL(X, idx_x, 14), + MMA8452_CHANNEL(Y, idx_y, 14), + MMA8452_CHANNEL(Z, idx_z, 14), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), +}; + static const struct iio_chan_spec mma8452_channels[] = { - MMA8452_CHANNEL(X, 0, 12), - MMA8452_CHANNEL(Y, 1, 12), - MMA8452_CHANNEL(Z, 2, 12), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8452_CHANNEL(X, idx_x, 12), + MMA8452_CHANNEL(Y, idx_y, 12), + MMA8452_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), }; static const struct iio_chan_spec mma8453_channels[] = { - MMA8452_CHANNEL(X, 0, 10), - MMA8452_CHANNEL(Y, 1, 10), - MMA8452_CHANNEL(Z, 2, 10), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8452_CHANNEL(X, idx_x, 10), + MMA8452_CHANNEL(Y, idx_y, 10), + MMA8452_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8452_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), }; static const struct iio_chan_spec mma8652_channels[] = { - MMA8652_CHANNEL(X, 0, 12), - MMA8652_CHANNEL(Y, 1, 12), - MMA8652_CHANNEL(Z, 2, 12), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8652_CHANNEL(X, idx_x, 12), + MMA8652_CHANNEL(Y, idx_y, 12), + MMA8652_CHANNEL(Z, idx_z, 12), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), }; static const struct iio_chan_spec mma8653_channels[] = { - MMA8652_CHANNEL(X, 0, 10), - MMA8652_CHANNEL(Y, 1, 10), - MMA8652_CHANNEL(Z, 2, 10), - IIO_CHAN_SOFT_TIMESTAMP(3), + MMA8652_CHANNEL(X, idx_x, 10), + MMA8652_CHANNEL(Y, idx_y, 10), + MMA8652_CHANNEL(Z, idx_z, 10), + IIO_CHAN_SOFT_TIMESTAMP(idx_ts), + MMA8652_FREEFALL_CHANNEL(IIO_MOD_X_AND_Y_AND_Z), }; enum { + mma8451, mma8452, mma8453, mma8652, mma8653, + fxls8471, }; static const struct mma_chip_info mma_chip_info_table[] = { - [mma8452] = { - .chip_id = MMA8452_DEVICE_ID, - .channels = mma8452_channels, - .num_channels = ARRAY_SIZE(mma8452_channels), + [mma8451] = { + .chip_id = MMA8451_DEVICE_ID, + .channels = mma8451_channels, + .num_channels = ARRAY_SIZE(mma8451_channels), /* * Hardware has fullscale of -2G, -4G, -8G corresponding to - * raw value -2048 for 12 bit or -512 for 10 bit. + * raw value -8192 for 14 bit, -2048 for 12 bit or -512 for 10 + * bit. * The userspace interface uses m/s^2 and we declare micro units * So scale factor for 12 bit here is given by: * g * N * 1000000 / 2048 for N = 2, 4, 8 and g=9.80665 */ + .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} }, + .ev_cfg = MMA8452_TRANSIENT_CFG, + .ev_cfg_ele = MMA8452_TRANSIENT_CFG_ELE, + .ev_cfg_chan_shift = 1, + .ev_src = MMA8452_TRANSIENT_SRC, + .ev_src_xe = MMA8452_TRANSIENT_SRC_XTRANSE, + .ev_src_ye = MMA8452_TRANSIENT_SRC_YTRANSE, + .ev_src_ze = MMA8452_TRANSIENT_SRC_ZTRANSE, + .ev_ths = MMA8452_TRANSIENT_THS, + .ev_ths_mask = MMA8452_TRANSIENT_THS_MASK, + .ev_count = MMA8452_TRANSIENT_COUNT, + }, + [mma8452] = { + .chip_id = MMA8452_DEVICE_ID, + .channels = mma8452_channels, + .num_channels = ARRAY_SIZE(mma8452_channels), .mma_scales = { {0, 9577}, {0, 19154}, {0, 38307} }, .ev_cfg = MMA8452_TRANSIENT_CFG, .ev_cfg_ele = MMA8452_TRANSIENT_CFG_ELE, @@ -922,6 +1149,22 @@ static const struct mma_chip_info mma_chip_info_table[] = { .ev_ths_mask = MMA8452_FF_MT_THS_MASK, .ev_count = MMA8452_FF_MT_COUNT, }, + [fxls8471] = { + .chip_id = FXLS8471_DEVICE_ID, + .channels = mma8451_channels, + .num_channels = ARRAY_SIZE(mma8451_channels), + .mma_scales = { {0, 2394}, {0, 4788}, {0, 9577} }, + .ev_cfg = MMA8452_TRANSIENT_CFG, + .ev_cfg_ele = MMA8452_TRANSIENT_CFG_ELE, + .ev_cfg_chan_shift = 1, + .ev_src = MMA8452_TRANSIENT_SRC, + .ev_src_xe = MMA8452_TRANSIENT_SRC_XTRANSE, + .ev_src_ye = MMA8452_TRANSIENT_SRC_YTRANSE, + .ev_src_ze = MMA8452_TRANSIENT_SRC_ZTRANSE, + .ev_ths = MMA8452_TRANSIENT_THS, + .ev_ths_mask = MMA8452_TRANSIENT_THS_MASK, + .ev_count = MMA8452_TRANSIENT_COUNT, + }, }; static struct attribute *mma8452_attributes[] = { @@ -955,7 +1198,11 @@ static int mma8452_data_rdy_trigger_set_state(struct iio_trigger *trig, { struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); struct mma8452_data *data = iio_priv(indio_dev); - int reg; + int reg, ret; + + ret = mma8452_set_runtime_pm_state(data->client, state); + if (ret) + return ret; reg = i2c_smbus_read_byte_data(data->client, MMA8452_CTRL_REG4); if (reg < 0) @@ -1042,10 +1289,12 @@ static int mma8452_reset(struct i2c_client *client) } static const struct of_device_id mma8452_dt_ids[] = { + { .compatible = "fsl,mma8451", .data = &mma_chip_info_table[mma8451] }, { .compatible = "fsl,mma8452", .data = &mma_chip_info_table[mma8452] }, { .compatible = "fsl,mma8453", .data = &mma_chip_info_table[mma8453] }, { .compatible = "fsl,mma8652", .data = &mma_chip_info_table[mma8652] }, { .compatible = "fsl,mma8653", .data = &mma_chip_info_table[mma8653] }, + { .compatible = "fsl,fxls8471", .data = &mma_chip_info_table[fxls8471] }, { } }; MODULE_DEVICE_TABLE(of, mma8452_dt_ids); @@ -1078,10 +1327,12 @@ static int mma8452_probe(struct i2c_client *client, return ret; switch (ret) { + case MMA8451_DEVICE_ID: case MMA8452_DEVICE_ID: case MMA8453_DEVICE_ID: case MMA8652_DEVICE_ID: case MMA8653_DEVICE_ID: + case FXLS8471_DEVICE_ID: if (ret == data->chip_info->chip_id) break; default: @@ -1130,13 +1381,21 @@ static int mma8452_probe(struct i2c_client *client, MMA8452_INT_FF_MT; int enabled_interrupts = MMA8452_INT_TRANS | MMA8452_INT_FF_MT; + int irq2; - /* Assume wired to INT1 pin */ - ret = i2c_smbus_write_byte_data(client, - MMA8452_CTRL_REG5, - supported_interrupts); - if (ret < 0) - return ret; + irq2 = of_irq_get_byname(client->dev.of_node, "INT2"); + + if (irq2 == client->irq) { + dev_dbg(&client->dev, "using interrupt line INT2\n"); + } else { + ret = i2c_smbus_write_byte_data(client, + MMA8452_CTRL_REG5, + supported_interrupts); + if (ret < 0) + return ret; + + dev_dbg(&client->dev, "using interrupt line INT1\n"); + } ret = i2c_smbus_write_byte_data(client, MMA8452_CTRL_REG4, @@ -1171,10 +1430,23 @@ static int mma8452_probe(struct i2c_client *client, goto buffer_cleanup; } + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto buffer_cleanup; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + MMA8452_AUTO_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); if (ret < 0) goto buffer_cleanup; + ret = mma8452_set_freefall_mode(data, false); + if (ret) + return ret; + return 0; buffer_cleanup: @@ -1191,6 +1463,11 @@ static int mma8452_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + iio_triggered_buffer_cleanup(indio_dev); mma8452_trigger_cleanup(indio_dev); mma8452_standby(iio_priv(indio_dev)); @@ -1198,6 +1475,45 @@ static int mma8452_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int mma8452_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma8452_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = mma8452_standby(data); + mutex_unlock(&data->lock); + if (ret < 0) { + dev_err(&data->client->dev, "powering off device failed\n"); + return -EAGAIN; + } + + return 0; +} + +static int mma8452_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct mma8452_data *data = iio_priv(indio_dev); + int ret, sleep_val; + + ret = mma8452_active(data); + if (ret < 0) + return ret; + + ret = mma8452_get_odr_index(data); + sleep_val = 1000 / mma8452_samp_freq[ret][0]; + if (sleep_val < 20) + usleep_range(sleep_val * 1000, 20000); + else + msleep_interruptible(sleep_val); + + return 0; +} +#endif + #ifdef CONFIG_PM_SLEEP static int mma8452_suspend(struct device *dev) { @@ -1210,18 +1526,21 @@ static int mma8452_resume(struct device *dev) return mma8452_active(iio_priv(i2c_get_clientdata( to_i2c_client(dev)))); } - -static SIMPLE_DEV_PM_OPS(mma8452_pm_ops, mma8452_suspend, mma8452_resume); -#define MMA8452_PM_OPS (&mma8452_pm_ops) -#else -#define MMA8452_PM_OPS NULL #endif +static const struct dev_pm_ops mma8452_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mma8452_suspend, mma8452_resume) + SET_RUNTIME_PM_OPS(mma8452_runtime_suspend, + mma8452_runtime_resume, NULL) +}; + static const struct i2c_device_id mma8452_id[] = { + { "mma8451", mma8451 }, { "mma8452", mma8452 }, { "mma8453", mma8453 }, { "mma8652", mma8652 }, { "mma8653", mma8653 }, + { "fxls8471", fxls8471 }, { } }; MODULE_DEVICE_TABLE(i2c, mma8452_id); @@ -1230,7 +1549,7 @@ static struct i2c_driver mma8452_driver = { .driver = { .name = "mma8452", .of_match_table = of_match_ptr(mma8452_dt_ids), - .pm = MMA8452_PM_OPS, + .pm = &mma8452_pm_ops, }, .probe = mma8452_probe, .remove = mma8452_remove, diff --git a/drivers/iio/accel/mma9551.c b/drivers/iio/accel/mma9551.c index 7db7cc0..d899a4d 100644 --- a/drivers/iio/accel/mma9551.c +++ b/drivers/iio/accel/mma9551.c @@ -495,25 +495,23 @@ static int mma9551_probe(struct i2c_client *client, if (ret < 0) goto out_poweroff; - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto out_poweroff; - } - ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto out_iio_unregister; + goto out_poweroff; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, MMA9551_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + return 0; -out_iio_unregister: - iio_device_unregister(indio_dev); out_poweroff: mma9551_set_device_state(client, false); @@ -525,11 +523,12 @@ static int mma9551_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma9551_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); mutex_lock(&data->mutex); mma9551_set_device_state(data->client, false); mutex_unlock(&data->mutex); diff --git a/drivers/iio/accel/mma9553.c b/drivers/iio/accel/mma9553.c index 9408ef3..bb05f3e 100644 --- a/drivers/iio/accel/mma9553.c +++ b/drivers/iio/accel/mma9553.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -1133,27 +1132,24 @@ static int mma9553_probe(struct i2c_client *client, } } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto out_poweroff; - } - ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto out_iio_unregister; + goto out_poweroff; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, MMA9551_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "unable to register iio device\n"); + goto out_poweroff; + } + dev_dbg(&indio_dev->dev, "Registered device %s\n", name); return 0; -out_iio_unregister: - iio_device_unregister(indio_dev); out_poweroff: mma9551_set_device_state(client, false); return ret; @@ -1164,11 +1160,12 @@ static int mma9553_remove(struct i2c_client *client) struct iio_dev *indio_dev = i2c_get_clientdata(client); struct mma9553_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); mutex_lock(&data->mutex); mma9551_set_device_state(data->client, false); mutex_unlock(&data->mutex); diff --git a/drivers/iio/accel/mxc4005.c b/drivers/iio/accel/mxc4005.c index e72e218..c23f47a 100644 --- a/drivers/iio/accel/mxc4005.c +++ b/drivers/iio/accel/mxc4005.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -380,31 +379,6 @@ static const struct iio_trigger_ops mxc4005_trigger_ops = { .owner = THIS_MODULE, }; -static int mxc4005_gpio_probe(struct i2c_client *client, - struct mxc4005_data *data) -{ - struct device *dev; - struct gpio_desc *gpio; - int ret; - - if (!client) - return -EINVAL; - - dev = &client->dev; - - gpio = devm_gpiod_get_index(dev, "mxc4005_int", 0, GPIOD_IN); - if (IS_ERR(gpio)) { - dev_err(dev, "failed to get acpi gpio index\n"); - return PTR_ERR(gpio); - } - - ret = gpiod_to_irq(gpio); - - dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), ret); - - return ret; -} - static int mxc4005_chip_init(struct mxc4005_data *data) { int ret; @@ -470,9 +444,6 @@ static int mxc4005_probe(struct i2c_client *client, return ret; } - if (client->irq < 0) - client->irq = mxc4005_gpio_probe(client, data); - if (client->irq > 0) { data->dready_trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", diff --git b/drivers/iio/accel/mxc6255.c b/drivers/iio/accel/mxc6255.c new file mode 100644 index 0000000..97ccde7 --- /dev/null +++ b/drivers/iio/accel/mxc6255.c @@ -0,0 +1,198 @@ +/* + * MXC6255 - MEMSIC orientation sensing accelerometer + * + * Copyright (c) 2015, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for MXC6255 (7-bit I2C slave address 0x15). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MXC6255_DRV_NAME "mxc6255" +#define MXC6255_REGMAP_NAME "mxc6255_regmap" + +#define MXC6255_REG_XOUT 0x00 +#define MXC6255_REG_YOUT 0x01 +#define MXC6255_REG_CHIP_ID 0x08 + +#define MXC6255_CHIP_ID 0x05 + +/* + * MXC6255 has only one measurement range: +/- 2G. + * The acceleration output is an 8-bit value. + * + * Scale is calculated as follows: + * (2 + 2) * 9.80665 / (2^8 - 1) = 0.153829 + * + * Scale value for +/- 2G measurement range + */ +#define MXC6255_SCALE 153829 + +enum mxc6255_axis { + AXIS_X, + AXIS_Y, +}; + +struct mxc6255_data { + struct i2c_client *client; + struct regmap *regmap; +}; + +static int mxc6255_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct mxc6255_data *data = iio_priv(indio_dev); + unsigned int reg; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(data->regmap, chan->address, ®); + if (ret < 0) { + dev_err(&data->client->dev, + "Error reading reg %lu\n", chan->address); + return ret; + } + + *val = sign_extend32(reg, 7); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = MXC6255_SCALE; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info mxc6255_info = { + .driver_module = THIS_MODULE, + .read_raw = mxc6255_read_raw, +}; + +#define MXC6255_CHANNEL(_axis, reg) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .address = reg, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mxc6255_channels[] = { + MXC6255_CHANNEL(X, MXC6255_REG_XOUT), + MXC6255_CHANNEL(Y, MXC6255_REG_YOUT), +}; + +static bool mxc6255_is_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MXC6255_REG_XOUT: + case MXC6255_REG_YOUT: + case MXC6255_REG_CHIP_ID: + return true; + default: + return false; + } +} + +static const struct regmap_config mxc6255_regmap_config = { + .name = MXC6255_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .readable_reg = mxc6255_is_readable_reg, +}; + +static int mxc6255_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mxc6255_data *data; + struct iio_dev *indio_dev; + struct regmap *regmap; + unsigned int chip_id; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + regmap = devm_regmap_init_i2c(client, &mxc6255_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Error initializing regmap\n"); + return PTR_ERR(regmap); + } + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + data->regmap = regmap; + + indio_dev->name = MXC6255_DRV_NAME; + indio_dev->dev.parent = &client->dev; + indio_dev->channels = mxc6255_channels; + indio_dev->num_channels = ARRAY_SIZE(mxc6255_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &mxc6255_info; + + ret = regmap_read(data->regmap, MXC6255_REG_CHIP_ID, &chip_id); + if (ret < 0) { + dev_err(&client->dev, "Error reading chip id %d\n", ret); + return ret; + } + + if (chip_id != MXC6255_CHIP_ID) { + dev_err(&client->dev, "Invalid chip id %x\n", chip_id); + return -ENODEV; + } + + dev_dbg(&client->dev, "Chip id %x\n", chip_id); + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Could not register IIO device\n"); + return ret; + } + + return 0; +} + +static const struct acpi_device_id mxc6255_acpi_match[] = { + {"MXC6255", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, mxc6255_acpi_match); + +static const struct i2c_device_id mxc6255_id[] = { + {"mxc6255", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, mxc6255_id); + +static struct i2c_driver mxc6255_driver = { + .driver = { + .name = MXC6255_DRV_NAME, + .acpi_match_table = ACPI_PTR(mxc6255_acpi_match), + }, + .probe = mxc6255_probe, + .id_table = mxc6255_id, +}; + +module_i2c_driver(mxc6255_driver); + +MODULE_AUTHOR("Teodora Baluta "); +MODULE_DESCRIPTION("MEMSIC MXC6255 orientation sensing accelerometer driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/accel/st_accel.h b/drivers/iio/accel/st_accel.h index 468f21f..57f83a6 100644 --- a/drivers/iio/accel/st_accel.h +++ b/drivers/iio/accel/st_accel.h @@ -14,6 +14,7 @@ #include #include +#define H3LIS331DL_DRIVER_NAME "h3lis331dl_accel" #define LIS3LV02DL_ACCEL_DEV_NAME "lis3lv02dl_accel" #define LSM303DLHC_ACCEL_DEV_NAME "lsm303dlhc_accel" #define LIS3DH_ACCEL_DEV_NAME "lis3dh" @@ -27,6 +28,7 @@ #define LSM303DLM_ACCEL_DEV_NAME "lsm303dlm_accel" #define LSM330_ACCEL_DEV_NAME "lsm330_accel" #define LSM303AGR_ACCEL_DEV_NAME "lsm303agr_accel" +#define LIS2DH12_ACCEL_DEV_NAME "lis2dh12_accel" /** * struct st_sensors_platform_data - default accel platform data diff --git a/drivers/iio/accel/st_accel_buffer.c b/drivers/iio/accel/st_accel_buffer.c index a1e642e..7fddc13 100644 --- a/drivers/iio/accel/st_accel_buffer.c +++ b/drivers/iio/accel/st_accel_buffer.c @@ -91,7 +91,7 @@ static const struct iio_buffer_setup_ops st_accel_buffer_setup_ops = { int st_accel_allocate_ring(struct iio_dev *indio_dev) { - return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + return iio_triggered_buffer_setup(indio_dev, NULL, &st_sensors_trigger_handler, &st_accel_buffer_setup_ops); } diff --git a/drivers/iio/accel/st_accel_core.c b/drivers/iio/accel/st_accel_core.c index 197a08b..4d95bfc 100644 --- a/drivers/iio/accel/st_accel_core.c +++ b/drivers/iio/accel/st_accel_core.c @@ -39,6 +39,9 @@ #define ST_ACCEL_FS_AVL_6G 6 #define ST_ACCEL_FS_AVL_8G 8 #define ST_ACCEL_FS_AVL_16G 16 +#define ST_ACCEL_FS_AVL_100G 100 +#define ST_ACCEL_FS_AVL_200G 200 +#define ST_ACCEL_FS_AVL_400G 400 /* CUSTOM VALUES FOR SENSOR 1 */ #define ST_ACCEL_1_WAI_EXP 0x33 @@ -67,6 +70,8 @@ #define ST_ACCEL_1_DRDY_IRQ_ADDR 0x22 #define ST_ACCEL_1_DRDY_IRQ_INT1_MASK 0x10 #define ST_ACCEL_1_DRDY_IRQ_INT2_MASK 0x08 +#define ST_ACCEL_1_IHL_IRQ_ADDR 0x25 +#define ST_ACCEL_1_IHL_IRQ_MASK 0x02 #define ST_ACCEL_1_MULTIREAD_BIT true /* CUSTOM VALUES FOR SENSOR 2 */ @@ -92,6 +97,10 @@ #define ST_ACCEL_2_DRDY_IRQ_ADDR 0x22 #define ST_ACCEL_2_DRDY_IRQ_INT1_MASK 0x02 #define ST_ACCEL_2_DRDY_IRQ_INT2_MASK 0x10 +#define ST_ACCEL_2_IHL_IRQ_ADDR 0x22 +#define ST_ACCEL_2_IHL_IRQ_MASK 0x80 +#define ST_ACCEL_2_OD_IRQ_ADDR 0x22 +#define ST_ACCEL_2_OD_IRQ_MASK 0x40 #define ST_ACCEL_2_MULTIREAD_BIT true /* CUSTOM VALUES FOR SENSOR 3 */ @@ -125,6 +134,8 @@ #define ST_ACCEL_3_DRDY_IRQ_ADDR 0x23 #define ST_ACCEL_3_DRDY_IRQ_INT1_MASK 0x80 #define ST_ACCEL_3_DRDY_IRQ_INT2_MASK 0x00 +#define ST_ACCEL_3_IHL_IRQ_ADDR 0x23 +#define ST_ACCEL_3_IHL_IRQ_MASK 0x40 #define ST_ACCEL_3_IG1_EN_ADDR 0x23 #define ST_ACCEL_3_IG1_EN_MASK 0x08 #define ST_ACCEL_3_MULTIREAD_BIT false @@ -169,10 +180,41 @@ #define ST_ACCEL_5_DRDY_IRQ_ADDR 0x22 #define ST_ACCEL_5_DRDY_IRQ_INT1_MASK 0x04 #define ST_ACCEL_5_DRDY_IRQ_INT2_MASK 0x20 +#define ST_ACCEL_5_IHL_IRQ_ADDR 0x22 +#define ST_ACCEL_5_IHL_IRQ_MASK 0x80 +#define ST_ACCEL_5_OD_IRQ_ADDR 0x22 +#define ST_ACCEL_5_OD_IRQ_MASK 0x40 #define ST_ACCEL_5_IG1_EN_ADDR 0x21 #define ST_ACCEL_5_IG1_EN_MASK 0x08 #define ST_ACCEL_5_MULTIREAD_BIT false +/* CUSTOM VALUES FOR SENSOR 6 */ +#define ST_ACCEL_6_WAI_EXP 0x32 +#define ST_ACCEL_6_ODR_ADDR 0x20 +#define ST_ACCEL_6_ODR_MASK 0x18 +#define ST_ACCEL_6_ODR_AVL_50HZ_VAL 0x00 +#define ST_ACCEL_6_ODR_AVL_100HZ_VAL 0x01 +#define ST_ACCEL_6_ODR_AVL_400HZ_VAL 0x02 +#define ST_ACCEL_6_ODR_AVL_1000HZ_VAL 0x03 +#define ST_ACCEL_6_PW_ADDR 0x20 +#define ST_ACCEL_6_PW_MASK 0x20 +#define ST_ACCEL_6_FS_ADDR 0x23 +#define ST_ACCEL_6_FS_MASK 0x30 +#define ST_ACCEL_6_FS_AVL_100_VAL 0x00 +#define ST_ACCEL_6_FS_AVL_200_VAL 0x01 +#define ST_ACCEL_6_FS_AVL_400_VAL 0x03 +#define ST_ACCEL_6_FS_AVL_100_GAIN IIO_G_TO_M_S_2(49000) +#define ST_ACCEL_6_FS_AVL_200_GAIN IIO_G_TO_M_S_2(98000) +#define ST_ACCEL_6_FS_AVL_400_GAIN IIO_G_TO_M_S_2(195000) +#define ST_ACCEL_6_BDU_ADDR 0x23 +#define ST_ACCEL_6_BDU_MASK 0x80 +#define ST_ACCEL_6_DRDY_IRQ_ADDR 0x22 +#define ST_ACCEL_6_DRDY_IRQ_INT1_MASK 0x02 +#define ST_ACCEL_6_DRDY_IRQ_INT2_MASK 0x10 +#define ST_ACCEL_6_IHL_IRQ_ADDR 0x22 +#define ST_ACCEL_6_IHL_IRQ_MASK 0x80 +#define ST_ACCEL_6_MULTIREAD_BIT true + static const struct iio_chan_spec st_accel_8bit_channels[] = { ST_SENSORS_LSM_CHANNELS(IIO_ACCEL, BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), @@ -232,6 +274,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { [3] = LSM330DL_ACCEL_DEV_NAME, [4] = LSM330DLC_ACCEL_DEV_NAME, [5] = LSM303AGR_ACCEL_DEV_NAME, + [6] = LIS2DH12_ACCEL_DEV_NAME, }, .ch = (struct iio_chan_spec *)st_accel_12bit_channels, .odr = { @@ -291,6 +334,9 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .addr = ST_ACCEL_1_DRDY_IRQ_ADDR, .mask_int1 = ST_ACCEL_1_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_ACCEL_1_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_ACCEL_1_IHL_IRQ_ADDR, + .mask_ihl = ST_ACCEL_1_IHL_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_ACCEL_1_MULTIREAD_BIT, .bootime = 2, @@ -354,6 +400,11 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .addr = ST_ACCEL_2_DRDY_IRQ_ADDR, .mask_int1 = ST_ACCEL_2_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_ACCEL_2_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_ACCEL_2_IHL_IRQ_ADDR, + .mask_ihl = ST_ACCEL_2_IHL_IRQ_MASK, + .addr_od = ST_ACCEL_2_OD_IRQ_ADDR, + .mask_od = ST_ACCEL_2_OD_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_ACCEL_2_MULTIREAD_BIT, .bootime = 2, @@ -429,6 +480,9 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .addr = ST_ACCEL_3_DRDY_IRQ_ADDR, .mask_int1 = ST_ACCEL_3_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_ACCEL_3_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_ACCEL_3_IHL_IRQ_ADDR, + .mask_ihl = ST_ACCEL_3_IHL_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, .ig1 = { .en_addr = ST_ACCEL_3_IG1_EN_ADDR, .en_mask = ST_ACCEL_3_IG1_EN_MASK, @@ -487,6 +541,7 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .drdy_irq = { .addr = ST_ACCEL_4_DRDY_IRQ_ADDR, .mask_int1 = ST_ACCEL_4_DRDY_IRQ_INT1_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_ACCEL_4_MULTIREAD_BIT, .bootime = 2, /* guess */ @@ -536,10 +591,77 @@ static const struct st_sensor_settings st_accel_sensors_settings[] = { .addr = ST_ACCEL_5_DRDY_IRQ_ADDR, .mask_int1 = ST_ACCEL_5_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_ACCEL_5_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_ACCEL_5_IHL_IRQ_ADDR, + .mask_ihl = ST_ACCEL_5_IHL_IRQ_MASK, + .addr_od = ST_ACCEL_5_OD_IRQ_ADDR, + .mask_od = ST_ACCEL_5_OD_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_ACCEL_5_MULTIREAD_BIT, .bootime = 2, /* guess */ }, + { + .wai = ST_ACCEL_6_WAI_EXP, + .wai_addr = ST_SENSORS_DEFAULT_WAI_ADDRESS, + .sensors_supported = { + [0] = H3LIS331DL_DRIVER_NAME, + }, + .ch = (struct iio_chan_spec *)st_accel_12bit_channels, + .odr = { + .addr = ST_ACCEL_6_ODR_ADDR, + .mask = ST_ACCEL_6_ODR_MASK, + .odr_avl = { + { 50, ST_ACCEL_6_ODR_AVL_50HZ_VAL }, + { 100, ST_ACCEL_6_ODR_AVL_100HZ_VAL, }, + { 400, ST_ACCEL_6_ODR_AVL_400HZ_VAL, }, + { 1000, ST_ACCEL_6_ODR_AVL_1000HZ_VAL, }, + }, + }, + .pw = { + .addr = ST_ACCEL_6_PW_ADDR, + .mask = ST_ACCEL_6_PW_MASK, + .value_on = ST_SENSORS_DEFAULT_POWER_ON_VALUE, + .value_off = ST_SENSORS_DEFAULT_POWER_OFF_VALUE, + }, + .enable_axis = { + .addr = ST_SENSORS_DEFAULT_AXIS_ADDR, + .mask = ST_SENSORS_DEFAULT_AXIS_MASK, + }, + .fs = { + .addr = ST_ACCEL_6_FS_ADDR, + .mask = ST_ACCEL_6_FS_MASK, + .fs_avl = { + [0] = { + .num = ST_ACCEL_FS_AVL_100G, + .value = ST_ACCEL_6_FS_AVL_100_VAL, + .gain = ST_ACCEL_6_FS_AVL_100_GAIN, + }, + [1] = { + .num = ST_ACCEL_FS_AVL_200G, + .value = ST_ACCEL_6_FS_AVL_200_VAL, + .gain = ST_ACCEL_6_FS_AVL_200_GAIN, + }, + [2] = { + .num = ST_ACCEL_FS_AVL_400G, + .value = ST_ACCEL_6_FS_AVL_400_VAL, + .gain = ST_ACCEL_6_FS_AVL_400_GAIN, + }, + }, + }, + .bdu = { + .addr = ST_ACCEL_6_BDU_ADDR, + .mask = ST_ACCEL_6_BDU_MASK, + }, + .drdy_irq = { + .addr = ST_ACCEL_6_DRDY_IRQ_ADDR, + .mask_int1 = ST_ACCEL_6_DRDY_IRQ_INT1_MASK, + .mask_int2 = ST_ACCEL_6_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_ACCEL_6_IHL_IRQ_ADDR, + .mask_ihl = ST_ACCEL_6_IHL_IRQ_MASK, + }, + .multi_read_bit = ST_ACCEL_6_MULTIREAD_BIT, + .bootime = 2, + }, }; static int st_accel_read_raw(struct iio_dev *indio_dev, @@ -619,6 +741,7 @@ static const struct iio_info accel_info = { static const struct iio_trigger_ops st_accel_trigger_ops = { .owner = THIS_MODULE, .set_trigger_state = ST_ACCEL_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, }; #define ST_ACCEL_TRIGGER_OPS (&st_accel_trigger_ops) #else diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c index 8b9cc84..7333ee9 100644 --- a/drivers/iio/accel/st_accel_i2c.c +++ b/drivers/iio/accel/st_accel_i2c.c @@ -72,6 +72,14 @@ static const struct of_device_id st_accel_of_match[] = { .compatible = "st,lsm303agr-accel", .data = LSM303AGR_ACCEL_DEV_NAME, }, + { + .compatible = "st,lis2dh12-accel", + .data = LIS2DH12_ACCEL_DEV_NAME, + }, + { + .compatible = "st,h3lis331dl-accel", + .data = H3LIS331DL_DRIVER_NAME, + }, {}, }; MODULE_DEVICE_TABLE(of, st_accel_of_match); @@ -121,6 +129,7 @@ static const struct i2c_device_id st_accel_id_table[] = { { LSM303DLM_ACCEL_DEV_NAME }, { LSM330_ACCEL_DEV_NAME }, { LSM303AGR_ACCEL_DEV_NAME }, + { LIS2DH12_ACCEL_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(i2c, st_accel_id_table); diff --git a/drivers/iio/accel/st_accel_spi.c b/drivers/iio/accel/st_accel_spi.c index f71b0d3..fcd5847 100644 --- a/drivers/iio/accel/st_accel_spi.c +++ b/drivers/iio/accel/st_accel_spi.c @@ -58,6 +58,7 @@ static const struct spi_device_id st_accel_id_table[] = { { LSM303DLM_ACCEL_DEV_NAME }, { LSM330_ACCEL_DEV_NAME }, { LSM303AGR_ACCEL_DEV_NAME }, + { LIS2DH12_ACCEL_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(spi, st_accel_id_table); diff --git a/drivers/iio/accel/stk8312.c b/drivers/iio/accel/stk8312.c index 85fe7f7..e31023d 100644 --- a/drivers/iio/accel/stk8312.c +++ b/drivers/iio/accel/stk8312.c @@ -11,7 +11,6 @@ */ #include -#include #include #include #include diff --git a/drivers/iio/accel/stk8ba50.c b/drivers/iio/accel/stk8ba50.c index 5709d9e..300d955 100644 --- a/drivers/iio/accel/stk8ba50.c +++ b/drivers/iio/accel/stk8ba50.c @@ -11,7 +11,6 @@ */ #include -#include #include #include #include diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1e7aded..25378c5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -131,6 +131,17 @@ config AT91_ADC To compile this driver as a module, choose M here: the module will be called at91_adc. +config AT91_SAMA5D2_ADC + tristate "Atmel AT91 SAMA5D2 ADC" + depends on ARCH_AT91 || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for Atmel SAMA5D2 ADC which is + available on SAMA5D2 SoC family. + + To compile this driver as a module, choose M here: the module will be + called at91-sama5d2_adc. + config AXP288_ADC tristate "X-Powers AXP288 ADC driver" depends on MFD_AXP20X @@ -175,6 +186,7 @@ config DA9150_GPADC config EXYNOS_ADC tristate "Exynos ADC driver support" depends on ARCH_EXYNOS || ARCH_S3C24XX || ARCH_S3C64XX || (OF && COMPILE_TEST) + depends on HAS_IOMEM help Core support for the ADC block found in the Samsung EXYNOS series of SoCs for drivers such as the touchscreen and hwmon to use to share @@ -183,6 +195,13 @@ config EXYNOS_ADC To compile this driver as a module, choose M here: the module will be called exynos_adc. +config FSL_MX25_ADC + tristate "Freescale MX25 ADC driver" + depends on MFD_MX25_TSADC + help + Generic Conversion Queue driver used for general purpose ADC in the + MX25. This driver supports single measurements using the MX25 ADC. + config HI8435 tristate "Holt Integrated Circuits HI-8435 threshold detector" select IIO_TRIGGERED_EVENT @@ -194,6 +213,26 @@ config HI8435 This driver can also be built as a module. If so, the module will be called hi8435. +config INA2XX_ADC + tristate "Texas Instruments INA2xx Power Monitors IIO driver" + depends on I2C && !SENSORS_INA2XX + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say yes here to build support for TI INA2xx family of Power Monitors. + This driver is mutually exclusive with the HWMON version. + +config IMX7D_ADC + tristate "IMX7D ADC driver" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + Say yes here to build support for IMX7D ADC. + + This driver can also be built as a module. If so, the module will be + called imx7d_adc. + config LP8788_ADC tristate "LP8788 ADC driver" depends on MFD_LP8788 @@ -203,6 +242,16 @@ config LP8788_ADC To compile this driver as a module, choose M here: the module will be called lp8788_adc. +config LPC18XX_ADC + tristate "NXP LPC18xx ADC driver" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on OF && HAS_IOMEM + help + Say yes here to build support for NXP LPC18XX ADC. + + To compile this driver as a module, choose M here: the module will be + called lpc18xx_adc. + config MAX1027 tristate "Maxim max1027 ADC driver" depends on SPI @@ -246,11 +295,11 @@ config MCP320X called mcp320x. config MCP3422 - tristate "Microchip Technology MCP3422/3/4/6/7/8 driver" + tristate "Microchip Technology MCP3421/2/3/4/5/6/7/8 driver" depends on I2C help - Say yes here to build support for Microchip Technology's - MCP3422, MCP3423, MCP3424, MCP3426, MCP3427 or MCP3428 + Say yes here to build support for Microchip Technology's MCP3421 + MCP3422, MCP3423, MCP3424, MCP3425, MCP3426, MCP3427 or MCP3428 analog to digital converters. This driver can also be built as a module. If so, the module will be @@ -266,6 +315,20 @@ config MEN_Z188_ADC This driver can also be built as a module. If so, the module will be called men_z188_adc. +config MXS_LRADC + tristate "Freescale i.MX23/i.MX28 LRADC" + depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM + depends on INPUT + select STMP_DEVICE + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes here to build support for i.MX23/i.MX28 LRADC convertor + built into these chips. + + To compile this driver as a module, choose M here: the + module will be called mxs-lradc. + config NAU7802 tristate "Nuvoton NAU7802 ADC driver" depends on I2C @@ -275,6 +338,14 @@ config NAU7802 To compile this driver as a module, choose M here: the module will be called nau7802. +config PALMAS_GPADC + tristate "TI Palmas General Purpose ADC" + depends on MFD_PALMAS + help + Palmas series pmic chip by Texas Instruments (twl6035/6037) + is used in smartphones and tablets and supports a 16 channel + general purpose ADC. + config QCOM_SPMI_IADC tristate "Qualcomm SPMI PMIC current ADC" depends on SPMI @@ -314,25 +385,58 @@ config ROCKCHIP_SARADC module will be called rockchip_saradc. config TI_ADC081C - tristate "Texas Instruments ADC081C021/027" + tristate "Texas Instruments ADC081C/ADC101C/ADC121C family" depends on I2C help - If you say yes here you get support for Texas Instruments ADC081C021 - and ADC081C027 ADC chips. + If you say yes here you get support for Texas Instruments ADC081C, + ADC101C and ADC121C ADC chips. This driver can also be built as a module. If so, the module will be called ti-adc081c. +config TI_ADC0832 + tristate "Texas Instruments ADC0831/ADC0832/ADC0834/ADC0838" + depends on SPI + help + If you say yes here you get support for Texas Instruments ADC0831, + ADC0832, ADC0834, ADC0838 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-adc0832. + config TI_ADC128S052 - tristate "Texas Instruments ADC128S052/ADC122S021" + tristate "Texas Instruments ADC128S052/ADC122S021/ADC124S021" depends on SPI help - If you say yes here you get support for Texas Instruments ADC128S052 - and ADC122S021 chips. + If you say yes here you get support for Texas Instruments ADC128S052, + ADC122S021 and ADC124S021 chips. This driver can also be built as a module. If so, the module will be called ti-adc128s052. +config TI_ADS1015 + tristate "Texas Instruments ADS1015 ADC" + depends on I2C && !SENSORS_ADS1015 + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1015 + ADC chip. + + This driver can also be built as a module. If so, the module will be + called ti-ads1015. + +config TI_ADS8688 + tristate "Texas Instruments ADS8688" + depends on SPI && OF + help + If you say yes here you get support for Texas Instruments ADS8684 and + and ADS8688 ADC chips + + This driver can also be built as a module. If so, the module will be + called ti-ads8688. + config TI_AM335X_ADC tristate "TI's AM335X ADC driver" depends on MFD_TI_AM335X_TSCADC diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 99b37a9..38638d4 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -14,24 +14,34 @@ obj-$(CONFIG_AD7793) += ad7793.o obj-$(CONFIG_AD7887) += ad7887.o obj-$(CONFIG_AD799X) += ad799x.o obj-$(CONFIG_AT91_ADC) += at91_adc.o +obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o obj-$(CONFIG_AXP288_ADC) += axp288_adc.o obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o obj-$(CONFIG_CC10001_ADC) += cc10001_adc.o obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o +obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o +obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o +obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o obj-$(CONFIG_MAX1027) += max1027.o obj-$(CONFIG_MAX1363) += max1363.o obj-$(CONFIG_MCP320X) += mcp320x.o obj-$(CONFIG_MCP3422) += mcp3422.o obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o +obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o obj-$(CONFIG_NAU7802) += nau7802.o +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o +obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o obj-$(CONFIG_TI_ADC128S052) += ti-adc128s052.o +obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o +obj-$(CONFIG_TI_ADS8688) += ti-ads8688.o obj-$(CONFIG_TI_AM335X_ADC) += ti_am335x_adc.o obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o diff --git a/drivers/iio/adc/ad7793.c b/drivers/iio/adc/ad7793.c index 4d960d3..7b07bb6 100644 --- a/drivers/iio/adc/ad7793.c +++ b/drivers/iio/adc/ad7793.c @@ -478,10 +478,9 @@ static int ad7793_read_raw(struct iio_dev *indio_dev, *val2 = st-> scale_avail[(st->conf >> 8) & 0x7][1]; return IIO_VAL_INT_PLUS_NANO; - } else { - /* 1170mV / 2^23 * 6 */ - scale_uv = (1170ULL * 1000000000ULL * 6ULL); } + /* 1170mV / 2^23 * 6 */ + scale_uv = (1170ULL * 1000000000ULL * 6ULL); break; case IIO_TEMP: /* 1170mV / 0.81 mV/C / 2^23 */ diff --git a/drivers/iio/adc/ad799x.c b/drivers/iio/adc/ad799x.c index 01d7158..a3f5254 100644 --- a/drivers/iio/adc/ad799x.c +++ b/drivers/iio/adc/ad799x.c @@ -477,7 +477,7 @@ static int ad799x_read_event_value(struct iio_dev *indio_dev, if (ret < 0) return ret; *val = (ret >> chan->scan_type.shift) & - GENMASK(chan->scan_type.realbits - 1 , 0); + GENMASK(chan->scan_type.realbits - 1, 0); return IIO_VAL_INT; } diff --git b/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c new file mode 100644 index 0000000..e10dca3 --- /dev/null +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -0,0 +1,556 @@ +/* + * Atmel ADC driver for SAMA5D2 devices and compatible. + * + * Copyright (C) 2015 Atmel, + * 2015 Ludovic Desroches + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Control Register */ +#define AT91_SAMA5D2_CR 0x00 +/* Software Reset */ +#define AT91_SAMA5D2_CR_SWRST BIT(0) +/* Start Conversion */ +#define AT91_SAMA5D2_CR_START BIT(1) +/* Touchscreen Calibration */ +#define AT91_SAMA5D2_CR_TSCALIB BIT(2) +/* Comparison Restart */ +#define AT91_SAMA5D2_CR_CMPRST BIT(4) + +/* Mode Register */ +#define AT91_SAMA5D2_MR 0x04 +/* Trigger Selection */ +#define AT91_SAMA5D2_MR_TRGSEL(v) ((v) << 1) +/* ADTRG */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG0 0 +/* TIOA0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG1 1 +/* TIOA1 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG2 2 +/* TIOA2 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG3 3 +/* PWM event line 0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG4 4 +/* PWM event line 1 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG5 5 +/* TIOA3 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG6 6 +/* RTCOUT0 */ +#define AT91_SAMA5D2_MR_TRGSEL_TRIG7 7 +/* Sleep Mode */ +#define AT91_SAMA5D2_MR_SLEEP BIT(5) +/* Fast Wake Up */ +#define AT91_SAMA5D2_MR_FWUP BIT(6) +/* Prescaler Rate Selection */ +#define AT91_SAMA5D2_MR_PRESCAL(v) ((v) << AT91_SAMA5D2_MR_PRESCAL_OFFSET) +#define AT91_SAMA5D2_MR_PRESCAL_OFFSET 8 +#define AT91_SAMA5D2_MR_PRESCAL_MAX 0xff +#define AT91_SAMA5D2_MR_PRESCAL_MASK GENMASK(15, 8) +/* Startup Time */ +#define AT91_SAMA5D2_MR_STARTUP(v) ((v) << 16) +#define AT91_SAMA5D2_MR_STARTUP_MASK GENMASK(19, 16) +/* Analog Change */ +#define AT91_SAMA5D2_MR_ANACH BIT(23) +/* Tracking Time */ +#define AT91_SAMA5D2_MR_TRACKTIM(v) ((v) << 24) +#define AT91_SAMA5D2_MR_TRACKTIM_MAX 0xff +/* Transfer Time */ +#define AT91_SAMA5D2_MR_TRANSFER(v) ((v) << 28) +#define AT91_SAMA5D2_MR_TRANSFER_MAX 0x3 +/* Use Sequence Enable */ +#define AT91_SAMA5D2_MR_USEQ BIT(31) + +/* Channel Sequence Register 1 */ +#define AT91_SAMA5D2_SEQR1 0x08 +/* Channel Sequence Register 2 */ +#define AT91_SAMA5D2_SEQR2 0x0c +/* Channel Enable Register */ +#define AT91_SAMA5D2_CHER 0x10 +/* Channel Disable Register */ +#define AT91_SAMA5D2_CHDR 0x14 +/* Channel Status Register */ +#define AT91_SAMA5D2_CHSR 0x18 +/* Last Converted Data Register */ +#define AT91_SAMA5D2_LCDR 0x20 +/* Interrupt Enable Register */ +#define AT91_SAMA5D2_IER 0x24 +/* Interrupt Disable Register */ +#define AT91_SAMA5D2_IDR 0x28 +/* Interrupt Mask Register */ +#define AT91_SAMA5D2_IMR 0x2c +/* Interrupt Status Register */ +#define AT91_SAMA5D2_ISR 0x30 +/* Last Channel Trigger Mode Register */ +#define AT91_SAMA5D2_LCTMR 0x34 +/* Last Channel Compare Window Register */ +#define AT91_SAMA5D2_LCCWR 0x38 +/* Overrun Status Register */ +#define AT91_SAMA5D2_OVER 0x3c +/* Extended Mode Register */ +#define AT91_SAMA5D2_EMR 0x40 +/* Compare Window Register */ +#define AT91_SAMA5D2_CWR 0x44 +/* Channel Gain Register */ +#define AT91_SAMA5D2_CGR 0x48 + +/* Channel Offset Register */ +#define AT91_SAMA5D2_COR 0x4c +#define AT91_SAMA5D2_COR_DIFF_OFFSET 16 + +/* Channel Data Register 0 */ +#define AT91_SAMA5D2_CDR0 0x50 +/* Analog Control Register */ +#define AT91_SAMA5D2_ACR 0x94 +/* Touchscreen Mode Register */ +#define AT91_SAMA5D2_TSMR 0xb0 +/* Touchscreen X Position Register */ +#define AT91_SAMA5D2_XPOSR 0xb4 +/* Touchscreen Y Position Register */ +#define AT91_SAMA5D2_YPOSR 0xb8 +/* Touchscreen Pressure Register */ +#define AT91_SAMA5D2_PRESSR 0xbc +/* Trigger Register */ +#define AT91_SAMA5D2_TRGR 0xc0 +/* Correction Select Register */ +#define AT91_SAMA5D2_COSR 0xd0 +/* Correction Value Register */ +#define AT91_SAMA5D2_CVR 0xd4 +/* Channel Error Correction Register */ +#define AT91_SAMA5D2_CECR 0xd8 +/* Write Protection Mode Register */ +#define AT91_SAMA5D2_WPMR 0xe4 +/* Write Protection Status Register */ +#define AT91_SAMA5D2_WPSR 0xe8 +/* Version Register */ +#define AT91_SAMA5D2_VERSION 0xfc + +#define AT91_SAMA5D2_CHAN_SINGLE(num, addr) \ + { \ + .type = IIO_VOLTAGE, \ + .channel = num, \ + .address = addr, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 12, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .datasheet_name = "CH"#num, \ + .indexed = 1, \ + } + +#define AT91_SAMA5D2_CHAN_DIFF(num, num2, addr) \ + { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .channel = num, \ + .channel2 = num2, \ + .address = addr, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + }, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\ + .datasheet_name = "CH"#num"-CH"#num2, \ + .indexed = 1, \ + } + +#define at91_adc_readl(st, reg) readl_relaxed(st->base + reg) +#define at91_adc_writel(st, reg, val) writel_relaxed(val, st->base + reg) + +struct at91_adc_soc_info { + unsigned startup_time; + unsigned min_sample_rate; + unsigned max_sample_rate; +}; + +struct at91_adc_state { + void __iomem *base; + int irq; + struct clk *per_clk; + struct regulator *reg; + struct regulator *vref; + int vref_uv; + const struct iio_chan_spec *chan; + bool conversion_done; + u32 conversion_value; + struct at91_adc_soc_info soc_info; + wait_queue_head_t wq_data_available; + /* + * lock to prevent concurrent 'single conversion' requests through + * sysfs. + */ + struct mutex lock; +}; + +static const struct iio_chan_spec at91_adc_channels[] = { + AT91_SAMA5D2_CHAN_SINGLE(0, 0x50), + AT91_SAMA5D2_CHAN_SINGLE(1, 0x54), + AT91_SAMA5D2_CHAN_SINGLE(2, 0x58), + AT91_SAMA5D2_CHAN_SINGLE(3, 0x5c), + AT91_SAMA5D2_CHAN_SINGLE(4, 0x60), + AT91_SAMA5D2_CHAN_SINGLE(5, 0x64), + AT91_SAMA5D2_CHAN_SINGLE(6, 0x68), + AT91_SAMA5D2_CHAN_SINGLE(7, 0x6c), + AT91_SAMA5D2_CHAN_SINGLE(8, 0x70), + AT91_SAMA5D2_CHAN_SINGLE(9, 0x74), + AT91_SAMA5D2_CHAN_SINGLE(10, 0x78), + AT91_SAMA5D2_CHAN_SINGLE(11, 0x7c), + AT91_SAMA5D2_CHAN_DIFF(0, 1, 0x50), + AT91_SAMA5D2_CHAN_DIFF(2, 3, 0x58), + AT91_SAMA5D2_CHAN_DIFF(4, 5, 0x60), + AT91_SAMA5D2_CHAN_DIFF(6, 7, 0x68), + AT91_SAMA5D2_CHAN_DIFF(8, 9, 0x70), + AT91_SAMA5D2_CHAN_DIFF(10, 11, 0x78), +}; + +static unsigned at91_adc_startup_time(unsigned startup_time_min, + unsigned adc_clk_khz) +{ + const unsigned startup_lookup[] = { + 0, 8, 16, 24, + 64, 80, 96, 112, + 512, 576, 640, 704, + 768, 832, 896, 960 + }; + unsigned ticks_min, i; + + /* + * Since the adc frequency is checked before, there is no reason + * to not meet the startup time constraint. + */ + + ticks_min = startup_time_min * adc_clk_khz / 1000; + for (i = 0; i < ARRAY_SIZE(startup_lookup); i++) + if (startup_lookup[i] > ticks_min) + break; + + return i; +} + +static void at91_adc_setup_samp_freq(struct at91_adc_state *st, unsigned freq) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + unsigned f_per, prescal, startup, mr; + + f_per = clk_get_rate(st->per_clk); + prescal = (f_per / (2 * freq)) - 1; + + startup = at91_adc_startup_time(st->soc_info.startup_time, + freq / 1000); + + mr = at91_adc_readl(st, AT91_SAMA5D2_MR); + mr &= ~(AT91_SAMA5D2_MR_STARTUP_MASK | AT91_SAMA5D2_MR_PRESCAL_MASK); + mr |= AT91_SAMA5D2_MR_STARTUP(startup); + mr |= AT91_SAMA5D2_MR_PRESCAL(prescal); + at91_adc_writel(st, AT91_SAMA5D2_MR, mr); + + dev_dbg(&indio_dev->dev, "freq: %u, startup: %u, prescal: %u\n", + freq, startup, prescal); +} + +static unsigned at91_adc_get_sample_freq(struct at91_adc_state *st) +{ + unsigned f_adc, f_per = clk_get_rate(st->per_clk); + unsigned mr, prescal; + + mr = at91_adc_readl(st, AT91_SAMA5D2_MR); + prescal = (mr >> AT91_SAMA5D2_MR_PRESCAL_OFFSET) + & AT91_SAMA5D2_MR_PRESCAL_MAX; + f_adc = f_per / (2 * (prescal + 1)); + + return f_adc; +} + +static irqreturn_t at91_adc_interrupt(int irq, void *private) +{ + struct iio_dev *indio = private; + struct at91_adc_state *st = iio_priv(indio); + u32 status = at91_adc_readl(st, AT91_SAMA5D2_ISR); + u32 imr = at91_adc_readl(st, AT91_SAMA5D2_IMR); + + if (status & imr) { + st->conversion_value = at91_adc_readl(st, st->chan->address); + st->conversion_done = true; + wake_up_interruptible(&st->wq_data_available); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int at91_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + u32 cor = 0; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&st->lock); + + st->chan = chan; + + if (chan->differential) + cor = (BIT(chan->channel) | BIT(chan->channel2)) << + AT91_SAMA5D2_COR_DIFF_OFFSET; + + at91_adc_writel(st, AT91_SAMA5D2_COR, cor); + at91_adc_writel(st, AT91_SAMA5D2_CHER, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_IER, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_START); + + ret = wait_event_interruptible_timeout(st->wq_data_available, + st->conversion_done, + msecs_to_jiffies(1000)); + if (ret == 0) + ret = -ETIMEDOUT; + + if (ret > 0) { + *val = st->conversion_value; + if (chan->scan_type.sign == 's') + *val = sign_extend32(*val, 11); + ret = IIO_VAL_INT; + st->conversion_done = false; + } + + at91_adc_writel(st, AT91_SAMA5D2_IDR, BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel)); + + mutex_unlock(&st->lock); + return ret; + + case IIO_CHAN_INFO_SCALE: + *val = st->vref_uv / 1000; + if (chan->differential) + *val *= 2; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = at91_adc_get_sample_freq(st); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int at91_adc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + if (val < st->soc_info.min_sample_rate || + val > st->soc_info.max_sample_rate) + return -EINVAL; + + at91_adc_setup_samp_freq(st, val); + + return 0; +} + +static const struct iio_info at91_adc_info = { + .read_raw = &at91_adc_read_raw, + .write_raw = &at91_adc_write_raw, + .driver_module = THIS_MODULE, +}; + +static int at91_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct at91_adc_state *st; + struct resource *res; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->dev.parent = &pdev->dev; + indio_dev->name = dev_name(&pdev->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &at91_adc_info; + indio_dev->channels = at91_adc_channels; + indio_dev->num_channels = ARRAY_SIZE(at91_adc_channels); + + st = iio_priv(indio_dev); + + ret = of_property_read_u32(pdev->dev.of_node, + "atmel,min-sample-rate-hz", + &st->soc_info.min_sample_rate); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,min-sample-rate-hz\n"); + return ret; + } + + ret = of_property_read_u32(pdev->dev.of_node, + "atmel,max-sample-rate-hz", + &st->soc_info.max_sample_rate); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,max-sample-rate-hz\n"); + return ret; + } + + ret = of_property_read_u32(pdev->dev.of_node, "atmel,startup-time-ms", + &st->soc_info.startup_time); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,startup-time-ms\n"); + return ret; + } + + init_waitqueue_head(&st->wq_data_available); + mutex_init(&st->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + st->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(st->base)) + return PTR_ERR(st->base); + + st->irq = platform_get_irq(pdev, 0); + if (st->irq <= 0) { + if (!st->irq) + st->irq = -ENXIO; + + return st->irq; + } + + st->per_clk = devm_clk_get(&pdev->dev, "adc_clk"); + if (IS_ERR(st->per_clk)) + return PTR_ERR(st->per_clk); + + st->reg = devm_regulator_get(&pdev->dev, "vddana"); + if (IS_ERR(st->reg)) + return PTR_ERR(st->reg); + + st->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(st->vref)) + return PTR_ERR(st->vref); + + ret = devm_request_irq(&pdev->dev, st->irq, at91_adc_interrupt, 0, + pdev->dev.driver->name, indio_dev); + if (ret) + return ret; + + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_enable(st->vref); + if (ret) + goto reg_disable; + + st->vref_uv = regulator_get_voltage(st->vref); + if (st->vref_uv <= 0) { + ret = -EINVAL; + goto vref_disable; + } + + at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_SWRST); + at91_adc_writel(st, AT91_SAMA5D2_IDR, 0xffffffff); + /* + * Transfer field must be set to 2 according to the datasheet and + * allows different analog settings for each channel. + */ + at91_adc_writel(st, AT91_SAMA5D2_MR, + AT91_SAMA5D2_MR_TRANSFER(2) | AT91_SAMA5D2_MR_ANACH); + + at91_adc_setup_samp_freq(st, st->soc_info.min_sample_rate); + + ret = clk_prepare_enable(st->per_clk); + if (ret) + goto vref_disable; + + platform_set_drvdata(pdev, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto per_clk_disable_unprepare; + + dev_info(&pdev->dev, "version: %x\n", + readl_relaxed(st->base + AT91_SAMA5D2_VERSION)); + + return 0; + +per_clk_disable_unprepare: + clk_disable_unprepare(st->per_clk); +vref_disable: + regulator_disable(st->vref); +reg_disable: + regulator_disable(st->reg); + return ret; +} + +static int at91_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct at91_adc_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + clk_disable_unprepare(st->per_clk); + + regulator_disable(st->vref); + regulator_disable(st->reg); + + return 0; +} + +static const struct of_device_id at91_adc_dt_match[] = { + { + .compatible = "atmel,sama5d2-adc", + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, at91_adc_dt_match); + +static struct platform_driver at91_adc_driver = { + .probe = at91_adc_probe, + .remove = at91_adc_remove, + .driver = { + .name = "at91-sama5d2_adc", + .of_match_table = at91_adc_dt_match, + }, +}; +module_platform_driver(at91_adc_driver) + +MODULE_AUTHOR("Ludovic Desroches "); +MODULE_DESCRIPTION("Atmel AT91 SAMA5D2 ADC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/at91_adc.c b/drivers/iio/adc/at91_adc.c index 7b40925..52430ba 100644 --- a/drivers/iio/adc/at91_adc.c +++ b/drivers/iio/adc/at91_adc.c @@ -742,7 +742,7 @@ static int at91_adc_of_get_resolution(struct at91_adc_state *st, return count; } - resolutions = kmalloc(count * sizeof(*resolutions), GFP_KERNEL); + resolutions = kmalloc_array(count, sizeof(*resolutions), GFP_KERNEL); if (!resolutions) return -ENOMEM; @@ -797,8 +797,8 @@ static u32 calc_startup_ticks_9x5(u32 startup_time, u32 adc_clk_khz) * Startup Time = / ADC Clock */ const int startup_lookup[] = { - 0 , 8 , 16 , 24 , - 64 , 80 , 96 , 112, + 0, 8, 16, 24, + 64, 80, 96, 112, 512, 576, 640, 704, 768, 832, 896, 960 }; @@ -924,14 +924,14 @@ static int at91_adc_probe_dt(struct at91_adc_state *st, ret = -EINVAL; goto error_ret; } - trig->name = name; + trig->name = name; if (of_property_read_u32(trig_node, "trigger-value", &prop)) { dev_err(&idev->dev, "Missing trigger-value property in the DT.\n"); ret = -EINVAL; goto error_ret; } - trig->value = prop; + trig->value = prop; trig->is_external = of_property_read_bool(trig_node, "trigger-external"); i++; } diff --git a/drivers/iio/adc/axp288_adc.c b/drivers/iio/adc/axp288_adc.c index 0c904ed..7fd2494 100644 --- a/drivers/iio/adc/axp288_adc.c +++ b/drivers/iio/adc/axp288_adc.c @@ -46,7 +46,7 @@ struct axp288_adc_info { struct regmap *regmap; }; -static const struct iio_chan_spec const axp288_adc_channels[] = { +static const struct iio_chan_spec axp288_adc_channels[] = { { .indexed = 1, .type = IIO_TEMP, diff --git a/drivers/iio/adc/exynos_adc.c b/drivers/iio/adc/exynos_adc.c index 3a2dbb3..c15756d 100644 --- a/drivers/iio/adc/exynos_adc.c +++ b/drivers/iio/adc/exynos_adc.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -42,12 +43,18 @@ #include #include +#include + /* S3C/EXYNOS4412/5250 ADC_V1 registers definitions */ #define ADC_V1_CON(x) ((x) + 0x00) +#define ADC_V1_TSC(x) ((x) + 0x04) #define ADC_V1_DLY(x) ((x) + 0x08) #define ADC_V1_DATX(x) ((x) + 0x0C) +#define ADC_V1_DATY(x) ((x) + 0x10) +#define ADC_V1_UPDN(x) ((x) + 0x14) #define ADC_V1_INTCLR(x) ((x) + 0x18) #define ADC_V1_MUX(x) ((x) + 0x1c) +#define ADC_V1_CLRINTPNDNUP(x) ((x) + 0x20) /* S3C2410 ADC registers definitions */ #define ADC_S3C2410_MUX(x) ((x) + 0x18) @@ -71,6 +78,30 @@ #define ADC_S3C2410_DATX_MASK 0x3FF #define ADC_S3C2416_CON_RES_SEL (1u << 3) +/* touch screen always uses channel 0 */ +#define ADC_S3C2410_MUX_TS 0 + +/* ADCTSC Register Bits */ +#define ADC_S3C2443_TSC_UD_SEN (1u << 8) +#define ADC_S3C2410_TSC_YM_SEN (1u << 7) +#define ADC_S3C2410_TSC_YP_SEN (1u << 6) +#define ADC_S3C2410_TSC_XM_SEN (1u << 5) +#define ADC_S3C2410_TSC_XP_SEN (1u << 4) +#define ADC_S3C2410_TSC_PULL_UP_DISABLE (1u << 3) +#define ADC_S3C2410_TSC_AUTO_PST (1u << 2) +#define ADC_S3C2410_TSC_XY_PST(x) (((x) & 0x3) << 0) + +#define ADC_TSC_WAIT4INT (ADC_S3C2410_TSC_YM_SEN | \ + ADC_S3C2410_TSC_YP_SEN | \ + ADC_S3C2410_TSC_XP_SEN | \ + ADC_S3C2410_TSC_XY_PST(3)) + +#define ADC_TSC_AUTOPST (ADC_S3C2410_TSC_YM_SEN | \ + ADC_S3C2410_TSC_YP_SEN | \ + ADC_S3C2410_TSC_XP_SEN | \ + ADC_S3C2410_TSC_AUTO_PST | \ + ADC_S3C2410_TSC_XY_PST(0)) + /* Bit definitions for ADC_V2 */ #define ADC_V2_CON1_SOFT_RESET (1u << 2) @@ -88,7 +119,9 @@ /* Bit definitions common for ADC_V1 and ADC_V2 */ #define ADC_CON_EN_START (1u << 0) #define ADC_CON_EN_START_MASK (0x3 << 0) +#define ADC_DATX_PRESSED (1u << 15) #define ADC_DATX_MASK 0xFFF +#define ADC_DATY_MASK 0xFFF #define EXYNOS_ADC_TIMEOUT (msecs_to_jiffies(100)) @@ -98,17 +131,24 @@ struct exynos_adc { struct exynos_adc_data *data; struct device *dev; + struct input_dev *input; void __iomem *regs; struct regmap *pmu_map; struct clk *clk; struct clk *sclk; unsigned int irq; + unsigned int tsirq; + unsigned int delay; struct regulator *vdd; struct completion completion; u32 value; unsigned int version; + + bool read_ts; + u32 ts_x; + u32 ts_y; }; struct exynos_adc_data { @@ -197,6 +237,9 @@ static void exynos_adc_v1_init_hw(struct exynos_adc *info) /* Enable 12-bit ADC resolution */ con1 |= ADC_V1_CON_RES; writel(con1, ADC_V1_CON(info->regs)); + + /* set touchscreen delay */ + writel(info->delay, ADC_V1_DLY(info->regs)); } static void exynos_adc_v1_exit_hw(struct exynos_adc *info) @@ -480,8 +523,8 @@ static int exynos_read_raw(struct iio_dev *indio_dev, if (info->data->start_conv) info->data->start_conv(info, chan->address); - timeout = wait_for_completion_timeout - (&info->completion, EXYNOS_ADC_TIMEOUT); + timeout = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); if (timeout == 0) { dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); if (info->data->init_hw) @@ -498,13 +541,55 @@ static int exynos_read_raw(struct iio_dev *indio_dev, return ret; } +static int exynos_read_s3c64xx_ts(struct iio_dev *indio_dev, int *x, int *y) +{ + struct exynos_adc *info = iio_priv(indio_dev); + unsigned long timeout; + int ret; + + mutex_lock(&indio_dev->mlock); + info->read_ts = true; + + reinit_completion(&info->completion); + + writel(ADC_S3C2410_TSC_PULL_UP_DISABLE | ADC_TSC_AUTOPST, + ADC_V1_TSC(info->regs)); + + /* Select the ts channel to be used and Trigger conversion */ + info->data->start_conv(info, ADC_S3C2410_MUX_TS); + + timeout = wait_for_completion_timeout(&info->completion, + EXYNOS_ADC_TIMEOUT); + if (timeout == 0) { + dev_warn(&indio_dev->dev, "Conversion timed out! Resetting\n"); + if (info->data->init_hw) + info->data->init_hw(info); + ret = -ETIMEDOUT; + } else { + *x = info->ts_x; + *y = info->ts_y; + ret = 0; + } + + info->read_ts = false; + mutex_unlock(&indio_dev->mlock); + + return ret; +} + static irqreturn_t exynos_adc_isr(int irq, void *dev_id) { struct exynos_adc *info = (struct exynos_adc *)dev_id; u32 mask = info->data->mask; /* Read value */ - info->value = readl(ADC_V1_DATX(info->regs)) & mask; + if (info->read_ts) { + info->ts_x = readl(ADC_V1_DATX(info->regs)); + info->ts_y = readl(ADC_V1_DATY(info->regs)); + writel(ADC_TSC_WAIT4INT | ADC_S3C2443_TSC_UD_SEN, ADC_V1_TSC(info->regs)); + } else { + info->value = readl(ADC_V1_DATX(info->regs)) & mask; + } /* clear irq */ if (info->data->clear_irq) @@ -515,6 +600,46 @@ static irqreturn_t exynos_adc_isr(int irq, void *dev_id) return IRQ_HANDLED; } +/* + * Here we (ab)use a threaded interrupt handler to stay running + * for as long as the touchscreen remains pressed, we report + * a new event with the latest data and then sleep until the + * next timer tick. This mirrors the behavior of the old + * driver, with much less code. + */ +static irqreturn_t exynos_ts_isr(int irq, void *dev_id) +{ + struct exynos_adc *info = dev_id; + struct iio_dev *dev = dev_get_drvdata(info->dev); + u32 x, y; + bool pressed; + int ret; + + while (info->input->users) { + ret = exynos_read_s3c64xx_ts(dev, &x, &y); + if (ret == -ETIMEDOUT) + break; + + pressed = x & y & ADC_DATX_PRESSED; + if (!pressed) { + input_report_key(info->input, BTN_TOUCH, 0); + input_sync(info->input); + break; + } + + input_report_abs(info->input, ABS_X, x & ADC_DATX_MASK); + input_report_abs(info->input, ABS_Y, y & ADC_DATY_MASK); + input_report_key(info->input, BTN_TOUCH, 1); + input_sync(info->input); + + msleep(1); + }; + + writel(0, ADC_V1_CLRINTPNDNUP(info->regs)); + + return IRQ_HANDLED; +} + static int exynos_adc_reg_access(struct iio_dev *indio_dev, unsigned reg, unsigned writeval, unsigned *readval) @@ -566,18 +691,72 @@ static int exynos_adc_remove_devices(struct device *dev, void *c) return 0; } +static int exynos_adc_ts_open(struct input_dev *dev) +{ + struct exynos_adc *info = input_get_drvdata(dev); + + enable_irq(info->tsirq); + + return 0; +} + +static void exynos_adc_ts_close(struct input_dev *dev) +{ + struct exynos_adc *info = input_get_drvdata(dev); + + disable_irq(info->tsirq); +} + +static int exynos_adc_ts_init(struct exynos_adc *info) +{ + int ret; + + if (info->tsirq <= 0) + return -ENODEV; + + info->input = input_allocate_device(); + if (!info->input) + return -ENOMEM; + + info->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + info->input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); + + input_set_abs_params(info->input, ABS_X, 0, 0x3FF, 0, 0); + input_set_abs_params(info->input, ABS_Y, 0, 0x3FF, 0, 0); + + info->input->name = "S3C24xx TouchScreen"; + info->input->id.bustype = BUS_HOST; + info->input->open = exynos_adc_ts_open; + info->input->close = exynos_adc_ts_close; + + input_set_drvdata(info->input, info); + + ret = input_register_device(info->input); + if (ret) { + input_free_device(info->input); + return ret; + } + + disable_irq(info->tsirq); + ret = request_threaded_irq(info->tsirq, NULL, exynos_ts_isr, + IRQF_ONESHOT, "touchscreen", info); + if (ret) + input_unregister_device(info->input); + + return ret; +} + static int exynos_adc_probe(struct platform_device *pdev) { struct exynos_adc *info = NULL; struct device_node *np = pdev->dev.of_node; + struct s3c2410_ts_mach_info *pdata = dev_get_platdata(&pdev->dev); struct iio_dev *indio_dev = NULL; struct resource *mem; + bool has_ts = false; int ret = -ENODEV; int irq; - if (!np) - return ret; - indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(struct exynos_adc)); if (!indio_dev) { dev_err(&pdev->dev, "failed allocating iio device\n"); @@ -613,8 +792,14 @@ static int exynos_adc_probe(struct platform_device *pdev) dev_err(&pdev->dev, "no irq resource?\n"); return irq; } - info->irq = irq; + + irq = platform_get_irq(pdev, 1); + if (irq == -EPROBE_DEFER) + return irq; + + info->tsirq = irq; + info->dev = &pdev->dev; init_completion(&info->completion); @@ -680,6 +865,22 @@ static int exynos_adc_probe(struct platform_device *pdev) if (info->data->init_hw) info->data->init_hw(info); + /* leave out any TS related code if unreachable */ + if (IS_REACHABLE(CONFIG_INPUT)) { + has_ts = of_property_read_bool(pdev->dev.of_node, + "has-touchscreen") || pdata; + } + + if (pdata) + info->delay = pdata->delay; + else + info->delay = 10000; + + if (has_ts) + ret = exynos_adc_ts_init(info); + if (ret) + goto err_iio; + ret = of_platform_populate(np, exynos_adc_match, NULL, &indio_dev->dev); if (ret < 0) { dev_err(&pdev->dev, "failed adding child nodes\n"); @@ -691,6 +892,11 @@ static int exynos_adc_probe(struct platform_device *pdev) err_of_populate: device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); + if (has_ts) { + input_unregister_device(info->input); + free_irq(info->tsirq, info); + } +err_iio: iio_device_unregister(indio_dev); err_irq: free_irq(info->irq, info); @@ -710,6 +916,10 @@ static int exynos_adc_remove(struct platform_device *pdev) struct iio_dev *indio_dev = platform_get_drvdata(pdev); struct exynos_adc *info = iio_priv(indio_dev); + if (IS_REACHABLE(CONFIG_INPUT)) { + free_irq(info->tsirq, info); + input_unregister_device(info->input); + } device_for_each_child(&indio_dev->dev, NULL, exynos_adc_remove_devices); iio_device_unregister(indio_dev); diff --git b/drivers/iio/adc/fsl-imx25-gcq.c b/drivers/iio/adc/fsl-imx25-gcq.c new file mode 100644 index 0000000..72b32c1 --- /dev/null +++ b/drivers/iio/adc/fsl-imx25-gcq.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann + * + * 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. + * + * This is the driver for the imx25 GCQ (Generic Conversion Queue) + * connected to the imx25 ADC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MX25_GCQ_TIMEOUT (msecs_to_jiffies(2000)) + +static const char * const driver_name = "mx25-gcq"; + +enum mx25_gcq_cfgs { + MX25_CFG_XP = 0, + MX25_CFG_YP, + MX25_CFG_XN, + MX25_CFG_YN, + MX25_CFG_WIPER, + MX25_CFG_INAUX0, + MX25_CFG_INAUX1, + MX25_CFG_INAUX2, + MX25_NUM_CFGS, +}; + +struct mx25_gcq_priv { + struct regmap *regs; + struct completion completed; + struct clk *clk; + int irq; + struct regulator *vref[4]; + u32 channel_vref_mv[MX25_NUM_CFGS]; +}; + +#define MX25_CQG_CHAN(chan, id) {\ + .type = IIO_VOLTAGE,\ + .indexed = 1,\ + .channel = chan,\ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE),\ + .datasheet_name = id,\ +} + +static const struct iio_chan_spec mx25_gcq_channels[MX25_NUM_CFGS] = { + MX25_CQG_CHAN(MX25_CFG_XP, "xp"), + MX25_CQG_CHAN(MX25_CFG_YP, "yp"), + MX25_CQG_CHAN(MX25_CFG_XN, "xn"), + MX25_CQG_CHAN(MX25_CFG_YN, "yn"), + MX25_CQG_CHAN(MX25_CFG_WIPER, "wiper"), + MX25_CQG_CHAN(MX25_CFG_INAUX0, "inaux0"), + MX25_CQG_CHAN(MX25_CFG_INAUX1, "inaux1"), + MX25_CQG_CHAN(MX25_CFG_INAUX2, "inaux2"), +}; + +static const char * const mx25_gcq_refp_names[] = { + [MX25_ADC_REFP_YP] = "yp", + [MX25_ADC_REFP_XP] = "xp", + [MX25_ADC_REFP_INT] = "int", + [MX25_ADC_REFP_EXT] = "ext", +}; + +static irqreturn_t mx25_gcq_irq(int irq, void *data) +{ + struct mx25_gcq_priv *priv = data; + u32 stats; + + regmap_read(priv->regs, MX25_ADCQ_SR, &stats); + + if (stats & MX25_ADCQ_SR_EOQ) { + regmap_update_bits(priv->regs, MX25_ADCQ_MR, + MX25_ADCQ_MR_EOQ_IRQ, MX25_ADCQ_MR_EOQ_IRQ); + complete(&priv->completed); + } + + /* Disable conversion queue run */ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, 0); + + /* Acknowledge all possible irqs */ + regmap_write(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR | + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | + MX25_ADCQ_SR_EOQ | MX25_ADCQ_SR_PD); + + return IRQ_HANDLED; +} + +static int mx25_gcq_get_raw_value(struct device *dev, + struct iio_chan_spec const *chan, + struct mx25_gcq_priv *priv, + int *val) +{ + long timeout; + u32 data; + + /* Setup the configuration we want to use */ + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0, + MX25_ADCQ_ITEM(0, chan->channel)); + + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_EOQ_IRQ, 0); + + /* Trigger queue for one run */ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FQS, + MX25_ADCQ_CR_FQS); + + timeout = wait_for_completion_interruptible_timeout( + &priv->completed, MX25_GCQ_TIMEOUT); + if (timeout < 0) { + dev_err(dev, "ADC wait for measurement failed\n"); + return timeout; + } else if (timeout == 0) { + dev_err(dev, "ADC timed out\n"); + return -ETIMEDOUT; + } + + regmap_read(priv->regs, MX25_ADCQ_FIFO, &data); + + *val = MX25_ADCQ_FIFO_DATA(data); + + return IIO_VAL_INT; +} + +static int mx25_gcq_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct mx25_gcq_priv *priv = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + ret = mx25_gcq_get_raw_value(&indio_dev->dev, chan, priv, val); + mutex_unlock(&indio_dev->mlock); + return ret; + + case IIO_CHAN_INFO_SCALE: + *val = priv->channel_vref_mv[chan->channel]; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info mx25_gcq_iio_info = { + .read_raw = mx25_gcq_read_raw, +}; + +static const struct regmap_config mx25_gcq_regconfig = { + .max_register = 0x5c, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static int mx25_gcq_setup_cfgs(struct platform_device *pdev, + struct mx25_gcq_priv *priv) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *child; + struct device *dev = &pdev->dev; + unsigned int refp_used[4] = {}; + int ret, i; + + /* + * Setup all configurations registers with a default conversion + * configuration for each input + */ + for (i = 0; i < MX25_NUM_CFGS; ++i) + regmap_write(priv->regs, MX25_ADCQ_CFG(i), + MX25_ADCQ_CFG_YPLL_OFF | + MX25_ADCQ_CFG_XNUR_OFF | + MX25_ADCQ_CFG_XPUL_OFF | + MX25_ADCQ_CFG_REFP_INT | + MX25_ADCQ_CFG_IN(i) | + MX25_ADCQ_CFG_REFN_NGND2); + + /* + * First get all regulators to store them in channel_vref_mv if + * necessary. Later we use that information for proper IIO scale + * information. + */ + priv->vref[MX25_ADC_REFP_INT] = NULL; + priv->vref[MX25_ADC_REFP_EXT] = + devm_regulator_get_optional(&pdev->dev, "vref-ext"); + priv->vref[MX25_ADC_REFP_XP] = + devm_regulator_get_optional(&pdev->dev, "vref-xp"); + priv->vref[MX25_ADC_REFP_YP] = + devm_regulator_get_optional(&pdev->dev, "vref-yp"); + + for_each_child_of_node(np, child) { + u32 reg; + u32 refp = MX25_ADCQ_CFG_REFP_INT; + u32 refn = MX25_ADCQ_CFG_REFN_NGND2; + + ret = of_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, "Failed to get reg property\n"); + return ret; + } + + if (reg >= MX25_NUM_CFGS) { + dev_err(dev, + "reg value is greater than the number of available configuration registers\n"); + return -EINVAL; + } + + of_property_read_u32(child, "fsl,adc-refp", &refp); + of_property_read_u32(child, "fsl,adc-refn", &refn); + + switch (refp) { + case MX25_ADC_REFP_EXT: + case MX25_ADC_REFP_XP: + case MX25_ADC_REFP_YP: + if (IS_ERR(priv->vref[refp])) { + dev_err(dev, "Error, trying to use external voltage reference without a vref-%s regulator.", + mx25_gcq_refp_names[refp]); + return PTR_ERR(priv->vref[refp]); + } + priv->channel_vref_mv[reg] = + regulator_get_voltage(priv->vref[refp]); + /* Conversion from uV to mV */ + priv->channel_vref_mv[reg] /= 1000; + break; + case MX25_ADC_REFP_INT: + priv->channel_vref_mv[reg] = 2500; + break; + default: + dev_err(dev, "Invalid positive reference %d\n", refp); + return -EINVAL; + } + + ++refp_used[refp]; + + /* + * Shift the read values to the correct positions within the + * register. + */ + refp = MX25_ADCQ_CFG_REFP(refp); + refn = MX25_ADCQ_CFG_REFN(refn); + + if ((refp & MX25_ADCQ_CFG_REFP_MASK) != refp) { + dev_err(dev, "Invalid fsl,adc-refp property value\n"); + return -EINVAL; + } + if ((refn & MX25_ADCQ_CFG_REFN_MASK) != refn) { + dev_err(dev, "Invalid fsl,adc-refn property value\n"); + return -EINVAL; + } + + regmap_update_bits(priv->regs, MX25_ADCQ_CFG(reg), + MX25_ADCQ_CFG_REFP_MASK | + MX25_ADCQ_CFG_REFN_MASK, + refp | refn); + } + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST, + MX25_ADCQ_CR_FRST | MX25_ADCQ_CR_QRST); + + regmap_write(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_PDMSK | MX25_ADCQ_CR_QSM_FQS); + + /* Remove unused regulators */ + for (i = 0; i != 4; ++i) { + if (!refp_used[i]) { + if (!IS_ERR_OR_NULL(priv->vref[i])) + devm_regulator_put(priv->vref[i]); + priv->vref[i] = NULL; + } + } + + return 0; +} + +static int mx25_gcq_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct mx25_gcq_priv *priv; + struct mx25_tsadc *tsadc = dev_get_drvdata(pdev->dev.parent); + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *mem; + int ret; + int i; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mem = devm_ioremap_resource(dev, res); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_gcq_regconfig); + if (IS_ERR(priv->regs)) { + dev_err(dev, "Failed to initialize regmap\n"); + return PTR_ERR(priv->regs); + } + + init_completion(&priv->completed); + + ret = mx25_gcq_setup_cfgs(pdev, priv); + if (ret) + return ret; + + for (i = 0; i != 4; ++i) { + if (!priv->vref[i]) + continue; + + ret = regulator_enable(priv->vref[i]); + if (ret) + goto err_regulator_disable; + } + + priv->clk = tsadc->clk; + ret = clk_prepare_enable(priv->clk); + if (ret) { + dev_err(dev, "Failed to enable clock\n"); + goto err_vref_disable; + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq <= 0) { + dev_err(dev, "Failed to get IRQ\n"); + ret = priv->irq; + if (!ret) + ret = -ENXIO; + goto err_clk_unprepare; + } + + ret = request_irq(priv->irq, mx25_gcq_irq, 0, pdev->name, priv); + if (ret) { + dev_err(dev, "Failed requesting IRQ\n"); + goto err_clk_unprepare; + } + + indio_dev->dev.parent = &pdev->dev; + indio_dev->channels = mx25_gcq_channels; + indio_dev->num_channels = ARRAY_SIZE(mx25_gcq_channels); + indio_dev->info = &mx25_gcq_iio_info; + indio_dev->name = driver_name; + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Failed to register iio device\n"); + goto err_irq_free; + } + + platform_set_drvdata(pdev, indio_dev); + + return 0; + +err_irq_free: + free_irq(priv->irq, priv); +err_clk_unprepare: + clk_disable_unprepare(priv->clk); +err_vref_disable: + i = 4; +err_regulator_disable: + for (; i-- > 0;) { + if (priv->vref[i]) + regulator_disable(priv->vref[i]); + } + return ret; +} + +static int mx25_gcq_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct mx25_gcq_priv *priv = iio_priv(indio_dev); + int i; + + iio_device_unregister(indio_dev); + free_irq(priv->irq, priv); + clk_disable_unprepare(priv->clk); + for (i = 4; i-- > 0;) { + if (priv->vref[i]) + regulator_disable(priv->vref[i]); + } + + return 0; +} + +static const struct of_device_id mx25_gcq_ids[] = { + { .compatible = "fsl,imx25-gcq", }, + { /* Sentinel */ } +}; + +static struct platform_driver mx25_gcq_driver = { + .driver = { + .name = "mx25-gcq", + .of_match_table = mx25_gcq_ids, + }, + .probe = mx25_gcq_probe, + .remove = mx25_gcq_remove, +}; +module_platform_driver(mx25_gcq_driver); + +MODULE_DESCRIPTION("ADC driver for Freescale mx25"); +MODULE_AUTHOR("Markus Pargmann "); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/imx7d_adc.c b/drivers/iio/adc/imx7d_adc.c new file mode 100644 index 0000000..e2241ee --- /dev/null +++ b/drivers/iio/adc/imx7d_adc.c @@ -0,0 +1,609 @@ +/* + * Freescale i.MX7D ADC driver + * + * Copyright (C) 2015 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ADC register */ +#define IMX7D_REG_ADC_CH_A_CFG1 0x00 +#define IMX7D_REG_ADC_CH_A_CFG2 0x10 +#define IMX7D_REG_ADC_CH_B_CFG1 0x20 +#define IMX7D_REG_ADC_CH_B_CFG2 0x30 +#define IMX7D_REG_ADC_CH_C_CFG1 0x40 +#define IMX7D_REG_ADC_CH_C_CFG2 0x50 +#define IMX7D_REG_ADC_CH_D_CFG1 0x60 +#define IMX7D_REG_ADC_CH_D_CFG2 0x70 +#define IMX7D_REG_ADC_CH_SW_CFG 0x80 +#define IMX7D_REG_ADC_TIMER_UNIT 0x90 +#define IMX7D_REG_ADC_DMA_FIFO 0xa0 +#define IMX7D_REG_ADC_FIFO_STATUS 0xb0 +#define IMX7D_REG_ADC_INT_SIG_EN 0xc0 +#define IMX7D_REG_ADC_INT_EN 0xd0 +#define IMX7D_REG_ADC_INT_STATUS 0xe0 +#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0 +#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100 +#define IMX7D_REG_ADC_CH_SW_CNV_RSLT 0x110 +#define IMX7D_REG_ADC_DMA_FIFO_DAT 0x120 +#define IMX7D_REG_ADC_ADC_CFG 0x130 + +#define IMX7D_REG_ADC_CHANNEL_CFG2_BASE 0x10 +#define IMX7D_EACH_CHANNEL_REG_OFFSET 0x20 + +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24) + +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12) + +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29) + +#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31) +#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1) +#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0) + +#define IMX7D_REG_ADC_INT_CHA_COV_INT_EN BIT(8) +#define IMX7D_REG_ADC_INT_CHB_COV_INT_EN BIT(9) +#define IMX7D_REG_ADC_INT_CHC_COV_INT_EN BIT(10) +#define IMX7D_REG_ADC_INT_CHD_COV_INT_EN BIT(11) +#define IMX7D_REG_ADC_INT_CHANNEL_INT_EN \ + (IMX7D_REG_ADC_INT_CHA_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHB_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHC_COV_INT_EN | \ + IMX7D_REG_ADC_INT_CHD_COV_INT_EN) +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00 +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000 + +#define IMX7D_ADC_TIMEOUT msecs_to_jiffies(100) + +enum imx7d_adc_clk_pre_div { + IMX7D_ADC_ANALOG_CLK_PRE_DIV_4, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_8, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_16, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_32, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_64, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_128, +}; + +enum imx7d_adc_average_num { + IMX7D_ADC_AVERAGE_NUM_4, + IMX7D_ADC_AVERAGE_NUM_8, + IMX7D_ADC_AVERAGE_NUM_16, + IMX7D_ADC_AVERAGE_NUM_32, +}; + +struct imx7d_adc_feature { + enum imx7d_adc_clk_pre_div clk_pre_div; + enum imx7d_adc_average_num avg_num; + + u32 core_time_unit; /* impact the sample rate */ + + bool average_en; +}; + +struct imx7d_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + + u32 vref_uv; + u32 value; + u32 channel; + u32 pre_div_num; + + struct regulator *vref; + struct imx7d_adc_feature adc_feature; + + struct completion completion; +}; + +struct imx7d_adc_analogue_core_clk { + u32 pre_div; + u32 reg_config; +}; + +#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \ + .pre_div = (_pre_div), \ + .reg_config = (_reg_conf), \ +} + +static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = { + IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128), +}; + +#define IMX7D_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ +} + +static const struct iio_chan_spec imx7d_adc_iio_channels[] = { + IMX7D_ADC_CHAN(0), + IMX7D_ADC_CHAN(1), + IMX7D_ADC_CHAN(2), + IMX7D_ADC_CHAN(3), + IMX7D_ADC_CHAN(4), + IMX7D_ADC_CHAN(5), + IMX7D_ADC_CHAN(6), + IMX7D_ADC_CHAN(7), + IMX7D_ADC_CHAN(8), + IMX7D_ADC_CHAN(9), + IMX7D_ADC_CHAN(10), + IMX7D_ADC_CHAN(11), + IMX7D_ADC_CHAN(12), + IMX7D_ADC_CHAN(13), + IMX7D_ADC_CHAN(14), + IMX7D_ADC_CHAN(15), +}; + +static const u32 imx7d_adc_average_num[] = { + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32, +}; + +static void imx7d_adc_feature_config(struct imx7d_adc *info) +{ + info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4; + info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32; + info->adc_feature.core_time_unit = 1; + info->adc_feature.average_en = true; +} + +static void imx7d_adc_sample_rate_set(struct imx7d_adc *info) +{ + struct imx7d_adc_feature *adc_feature = &info->adc_feature; + struct imx7d_adc_analogue_core_clk adc_analogure_clk; + u32 i; + u32 tmp_cfg1; + u32 sample_rate = 0; + + /* + * Before sample set, disable channel A,B,C,D. Here we + * clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1. + */ + for (i = 0; i < 4; i++) { + tmp_cfg1 = + readl(info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN; + writel(tmp_cfg1, + info->regs + i * IMX7D_EACH_CHANNEL_REG_OFFSET); + } + + adc_analogure_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div]; + sample_rate |= adc_analogure_clk.reg_config; + info->pre_div_num = adc_analogure_clk.pre_div; + + sample_rate |= adc_feature->core_time_unit; + writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT); +} + +static void imx7d_adc_hw_init(struct imx7d_adc *info) +{ + u32 cfg; + + /* power up and enable adc analogue core */ + cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN); + cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); + + /* enable channel A,B,C,D interrupt */ + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_SIG_EN); + writel(IMX7D_REG_ADC_INT_CHANNEL_INT_EN, + info->regs + IMX7D_REG_ADC_INT_EN); + + imx7d_adc_sample_rate_set(info); +} + +static void imx7d_adc_channel_set(struct imx7d_adc *info) +{ + u32 cfg1 = 0; + u32 cfg2; + u32 channel; + + channel = info->channel; + + /* the channel choose single conversion, and enable average mode */ + cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE); + if (info->adc_feature.average_en) + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN; + + /* + * physical channel 0 chose logical channel A + * physical channel 1 chose logical channel B + * physical channel 2 chose logical channel C + * physical channel 3 chose logical channel D + */ + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel); + + /* + * read register REG_ADC_CH_A\B\C\D_CFG2, according to the + * channel chosen + */ + cfg2 = readl(info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + + cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num]; + + /* + * write the register REG_ADC_CH_A\B\C\D_CFG2, according to + * the channel chosen + */ + writel(cfg2, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel + + IMX7D_REG_ADC_CHANNEL_CFG2_BASE); + writel(cfg1, info->regs + IMX7D_EACH_CHANNEL_REG_OFFSET * channel); +} + +static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info) +{ + /* input clock is always 24MHz */ + u32 input_clk = 24000000; + u32 analogue_core_clk; + u32 core_time_unit = info->adc_feature.core_time_unit; + u32 tmp; + + analogue_core_clk = input_clk / info->pre_div_num; + tmp = (core_time_unit + 1) * 6; + + return analogue_core_clk / tmp; +} + +static int imx7d_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + u32 channel; + long ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + reinit_completion(&info->completion); + + channel = chan->channel & 0x03; + info->channel = channel; + imx7d_adc_channel_set(info); + + ret = wait_for_completion_interruptible_timeout + (&info->completion, IMX7D_ADC_TIMEOUT); + if (ret == 0) { + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + if (ret < 0) { + mutex_unlock(&indio_dev->mlock); + return ret; + } + + *val = info->value; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + info->vref_uv = regulator_get_voltage(info->vref); + *val = info->vref_uv / 1000; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_SAMP_FREQ: + *val = imx7d_adc_get_sample_rate(info); + return IIO_VAL_INT; + + default: + return -EINVAL; + } +} + +static int imx7d_adc_read_data(struct imx7d_adc *info) +{ + u32 channel; + u32 value; + + channel = info->channel & 0x03; + + /* + * channel A and B conversion result share one register, + * bit[27~16] is the channel B conversion result, + * bit[11~0] is the channel A conversion result. + * channel C and D is the same. + */ + if (channel < 2) + value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT); + else + value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT); + if (channel & 0x1) /* channel B or D */ + value = (value >> 16) & 0xFFF; + else /* channel A or C */ + value &= 0xFFF; + + return value; +} + +static irqreturn_t imx7d_adc_isr(int irq, void *dev_id) +{ + struct imx7d_adc *info = (struct imx7d_adc *)dev_id; + int status; + + status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) { + info->value = imx7d_adc_read_data(info); + complete(&info->completion); + + /* + * The register IMX7D_REG_ADC_INT_STATUS can't clear + * itself after read operation, need software to write + * 0 to the related bit. Here we clear the channel A/B/C/D + * conversion finished flag. + */ + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + /* + * If the channel A/B/C/D conversion timeout, report it and clear these + * timeout flags. + */ + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) { + pr_err("%s: ADC got conversion time out interrupt: 0x%08x\n", + dev_name(info->dev), status); + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + return IRQ_HANDLED; +} + +static int imx7d_adc_reg_access(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, + unsigned *readval) +{ + struct imx7d_adc *info = iio_priv(indio_dev); + + if (!readval || reg % 4 || reg > IMX7D_REG_ADC_ADC_CFG) + return -EINVAL; + + *readval = readl(info->regs + reg); + + return 0; +} + +static const struct iio_info imx7d_adc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &imx7d_adc_read_raw, + .debugfs_reg_access = &imx7d_adc_reg_access, +}; + +static const struct of_device_id imx7d_adc_match[] = { + { .compatible = "fsl,imx7d-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx7d_adc_match); + +static void imx7d_adc_power_down(struct imx7d_adc *info) +{ + u32 adc_cfg; + + adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN; + adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); +} + +static int imx7d_adc_probe(struct platform_device *pdev) +{ + struct imx7d_adc *info; + struct iio_dev *indio_dev; + struct resource *mem; + int irq; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) { + ret = PTR_ERR(info->regs); + dev_err(&pdev->dev, + "Failed to remap adc memory, err = %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No irq resource?\n"); + return irq; + } + + info->clk = devm_clk_get(&pdev->dev, "adc"); + if (IS_ERR(info->clk)) { + ret = PTR_ERR(info->clk); + dev_err(&pdev->dev, "Failed getting clock, err = %d\n", ret); + return ret; + } + + info->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(info->vref)) { + ret = PTR_ERR(info->vref); + dev_err(&pdev->dev, + "Failed getting reference voltage, err = %d\n", ret); + return ret; + } + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(&pdev->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + platform_set_drvdata(pdev, indio_dev); + + init_completion(&info->completion); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &imx7d_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = imx7d_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(imx7d_adc_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock.\n"); + goto error_adc_clk_enable; + } + + ret = devm_request_irq(info->dev, irq, + imx7d_adc_isr, 0, + dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed requesting irq, irq = %d\n", irq); + goto error_iio_device_register; + } + + imx7d_adc_feature_config(info); + imx7d_adc_hw_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + imx7d_adc_power_down(info); + dev_err(&pdev->dev, "Couldn't register the device.\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + clk_disable_unprepare(info->clk); +error_adc_clk_enable: + regulator_disable(info->vref); + + return ret; +} + +static int imx7d_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct imx7d_adc *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + + imx7d_adc_power_down(info); + + clk_disable_unprepare(info->clk); + regulator_disable(info->vref); + + return 0; +} + +static int __maybe_unused imx7d_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct imx7d_adc *info = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(info->vref); + if (ret) { + dev_err(info->dev, + "Can't enable adc reference top voltage, err = %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(info->dev, + "Could not prepare or enable clock.\n"); + regulator_disable(info->vref); + return ret; + } + + imx7d_adc_hw_init(info); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(imx7d_adc_pm_ops, imx7d_adc_suspend, imx7d_adc_resume); + +static struct platform_driver imx7d_adc_driver = { + .probe = imx7d_adc_probe, + .remove = imx7d_adc_remove, + .driver = { + .name = "imx7d_adc", + .of_match_table = imx7d_adc_match, + .pm = &imx7d_adc_pm_ops, + }, +}; + +module_platform_driver(imx7d_adc_driver); + +MODULE_AUTHOR("Haibo Chen "); +MODULE_DESCRIPTION("Freeacale IMX7D ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/ina2xx-adc.c b/drivers/iio/adc/ina2xx-adc.c new file mode 100644 index 0000000..502f2fb --- /dev/null +++ b/drivers/iio/adc/ina2xx-adc.c @@ -0,0 +1,743 @@ +/* + * INA2XX Current and Power Monitors + * + * Copyright 2015 Baylibre SAS. + * + * 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. + * + * Based on linux/drivers/iio/adc/ad7291.c + * Copyright 2010-2011 Analog Devices Inc. + * + * Based on linux/drivers/hwmon/ina2xx.c + * Copyright 2012 Lothar Felten + * + * Licensed under the GPL-2 or later. + * + * IIO driver for INA219-220-226-230-231 + * + * Configurable 7-bit I2C slave address from 0x40 to 0x4F + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* INA2XX registers definition */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +#define INA226_ALERT_MASK GENMASK(2, 1) +#define INA266_CVRF BIT(3) + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ +#define INA226_CONFIG_DEFAULT 0x4327 +#define INA226_DEFAULT_AVG 4 +#define INA226_DEFAULT_IT 1110 + +#define INA2XX_RSHUNT_DEFAULT 10000 + +/* + * bit mask for reading the averaging setting in the configuration register + * FIXME: use regmap_fields. + */ +#define INA2XX_MODE_MASK GENMASK(3, 0) + +#define INA226_AVG_MASK GENMASK(11, 9) +#define INA226_SHIFT_AVG(val) ((val) << 9) + +/* Integration time for VBus */ +#define INA226_ITB_MASK GENMASK(8, 6) +#define INA226_SHIFT_ITB(val) ((val) << 6) + +/* Integration time for VShunt */ +#define INA226_ITS_MASK GENMASK(5, 3) +#define INA226_SHIFT_ITS(val) ((val) << 3) + +/* Cosmetic macro giving the sampling period for a full P=UxI cycle */ +#define SAMPLING_PERIOD(c) ((c->int_time_vbus + c->int_time_vshunt) \ + * c->avg) + +static bool ina2xx_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg == INA2XX_CONFIG) || (reg > INA2XX_CURRENT); +} + +static bool ina2xx_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg != INA2XX_CONFIG); +} + +static inline bool is_signed_reg(unsigned int reg) +{ + return (reg == INA2XX_SHUNT_VOLTAGE) || (reg == INA2XX_CURRENT); +} + +static const struct regmap_config ina2xx_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = INA2XX_MAX_REGISTERS, + .writeable_reg = ina2xx_is_writeable_reg, + .volatile_reg = ina2xx_is_volatile_reg, +}; + +enum ina2xx_ids { ina219, ina226 }; + +struct ina2xx_config { + u16 config_default; + int calibration_factor; + int shunt_div; + int bus_voltage_shift; + int bus_voltage_lsb; /* uV */ + int power_lsb; /* uW */ +}; + +struct ina2xx_chip_info { + struct regmap *regmap; + struct task_struct *task; + const struct ina2xx_config *config; + struct mutex state_lock; + unsigned int shunt_resistor; + int avg; + s64 prev_ns; /* track buffer capture time, check for underruns */ + int int_time_vbus; /* Bus voltage integration time uS */ + int int_time_vshunt; /* Shunt voltage integration time uS */ + bool allow_async_readout; +}; + +static const struct ina2xx_config ina2xx_config[] = { + [ina219] = { + .config_default = INA219_CONFIG_DEFAULT, + .calibration_factor = 40960000, + .shunt_div = 100, + .bus_voltage_shift = 3, + .bus_voltage_lsb = 4000, + .power_lsb = 20000, + }, + [ina226] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_factor = 5120000, + .shunt_div = 400, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1250, + .power_lsb = 25000, + }, +}; + +static int ina2xx_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int regval; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(chip->regmap, chan->address, ®val); + if (ret) + return ret; + + if (is_signed_reg(chan->address)) + *val = (s16) regval; + else + *val = regval; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = chip->avg; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + if (chan->address == INA2XX_SHUNT_VOLTAGE) + *val2 = chip->int_time_vshunt; + else + *val2 = chip->int_time_vbus; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SAMP_FREQ: + /* + * Sample freq is read only, it is a consequence of + * 1/AVG*(CT_bus+CT_shunt). + */ + *val = DIV_ROUND_CLOSEST(1000000, SAMPLING_PERIOD(chip)); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + case INA2XX_SHUNT_VOLTAGE: + /* processed (mV) = raw/shunt_div */ + *val2 = chip->config->shunt_div; + *val = 1; + return IIO_VAL_FRACTIONAL; + + case INA2XX_BUS_VOLTAGE: + /* processed (mV) = raw*lsb (uV) / (1000 << shift) */ + *val = chip->config->bus_voltage_lsb; + *val2 = 1000 << chip->config->bus_voltage_shift; + return IIO_VAL_FRACTIONAL; + + case INA2XX_POWER: + /* processed (mW) = raw*lsb (uW) / 1000 */ + *val = chip->config->power_lsb; + *val2 = 1000; + return IIO_VAL_FRACTIONAL; + + case INA2XX_CURRENT: + /* processed (mA) = raw (mA) */ + *val = 1; + return IIO_VAL_INT; + } + } + + return -EINVAL; +} + +/* + * Available averaging rates for ina226. The indices correspond with + * the bit values expected by the chip (according to the ina226 datasheet, + * table 3 AVG bit settings, found at + * http://www.ti.com/lit/ds/symlink/ina226.pdf. + */ +static const int ina226_avg_tab[] = { 1, 4, 16, 64, 128, 256, 512, 1024 }; + +static int ina226_set_average(struct ina2xx_chip_info *chip, unsigned int val, + unsigned int *config) +{ + int bits; + + if (val > 1024 || val < 1) + return -EINVAL; + + bits = find_closest(val, ina226_avg_tab, + ARRAY_SIZE(ina226_avg_tab)); + + chip->avg = ina226_avg_tab[bits]; + + *config &= ~INA226_AVG_MASK; + *config |= INA226_SHIFT_AVG(bits) & INA226_AVG_MASK; + + return 0; +} + +/* Conversion times in uS */ +static const int ina226_conv_time_tab[] = { 140, 204, 332, 588, 1100, + 2116, 4156, 8244 }; + +static int ina226_set_int_time_vbus(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vbus = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITB_MASK; + *config |= INA226_SHIFT_ITB(bits) & INA226_ITB_MASK; + + return 0; +} + +static int ina226_set_int_time_vshunt(struct ina2xx_chip_info *chip, + unsigned int val_us, unsigned int *config) +{ + int bits; + + if (val_us > 8244 || val_us < 140) + return -EINVAL; + + bits = find_closest(val_us, ina226_conv_time_tab, + ARRAY_SIZE(ina226_conv_time_tab)); + + chip->int_time_vshunt = ina226_conv_time_tab[bits]; + + *config &= ~INA226_ITS_MASK; + *config |= INA226_SHIFT_ITS(bits) & INA226_ITS_MASK; + + return 0; +} + +static int ina2xx_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int config, tmp; + int ret; + + if (iio_buffer_enabled(indio_dev)) + return -EBUSY; + + mutex_lock(&chip->state_lock); + + ret = regmap_read(chip->regmap, INA2XX_CONFIG, &config); + if (ret) + goto err; + + tmp = config; + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = ina226_set_average(chip, val, &tmp); + break; + + case IIO_CHAN_INFO_INT_TIME: + if (chan->address == INA2XX_SHUNT_VOLTAGE) + ret = ina226_set_int_time_vshunt(chip, val2, &tmp); + else + ret = ina226_set_int_time_vbus(chip, val2, &tmp); + break; + + default: + ret = -EINVAL; + } + + if (!ret && (tmp != config)) + ret = regmap_write(chip->regmap, INA2XX_CONFIG, tmp); +err: + mutex_unlock(&chip->state_lock); + + return ret; +} + +static ssize_t ina2xx_allow_async_readout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->allow_async_readout); +} + +static ssize_t ina2xx_allow_async_readout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + bool val; + int ret; + + ret = strtobool((const char *) buf, &val); + if (ret) + return ret; + + chip->allow_async_readout = val; + + return len; +} + +/* + * Set current LSB to 1mA, shunt is in uOhms + * (equation 13 in datasheet). We hardcode a Current_LSB + * of 1.0 x10-6. The only remaining parameter is RShunt. + * There is no need to expose the CALIBRATION register + * to the user for now. But we need to reset this register + * if the user updates RShunt after driver init, e.g upon + * reading an EEPROM/Probe-type value. + */ +static int ina2xx_set_calibration(struct ina2xx_chip_info *chip) +{ + u16 regval = DIV_ROUND_CLOSEST(chip->config->calibration_factor, + chip->shunt_resistor); + + return regmap_write(chip->regmap, INA2XX_CALIBRATION, regval); +} + +static int set_shunt_resistor(struct ina2xx_chip_info *chip, unsigned int val) +{ + if (val <= 0 || val > chip->config->calibration_factor) + return -EINVAL; + + chip->shunt_resistor = val; + + return 0; +} + +static ssize_t ina2xx_shunt_resistor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "%d\n", chip->shunt_resistor); +} + +static ssize_t ina2xx_shunt_resistor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct ina2xx_chip_info *chip = iio_priv(dev_to_iio_dev(dev)); + unsigned long val; + int ret; + + ret = kstrtoul((const char *) buf, 10, &val); + if (ret) + return ret; + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + /* Update the Calibration register */ + ret = ina2xx_set_calibration(chip); + if (ret) + return ret; + + return len; +} + +#define INA2XX_CHAN(_type, _index, _address) { \ + .type = (_type), \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +/* + * Sampling Freq is a consequence of the integration times of + * the Voltage channels. + */ +#define INA2XX_CHAN_VOLTAGE(_index, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_INT_TIME), \ + .scan_index = (_index), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + } \ +} + +static const struct iio_chan_spec ina2xx_channels[] = { + INA2XX_CHAN_VOLTAGE(0, INA2XX_SHUNT_VOLTAGE), + INA2XX_CHAN_VOLTAGE(1, INA2XX_BUS_VOLTAGE), + INA2XX_CHAN(IIO_POWER, 2, INA2XX_POWER), + INA2XX_CHAN(IIO_CURRENT, 3, INA2XX_CURRENT), + IIO_CHAN_SOFT_TIMESTAMP(4), +}; + +static int ina2xx_work_buffer(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned short data[8]; + int bit, ret, i = 0; + s64 time_a, time_b; + unsigned int alert; + + time_a = iio_get_time_ns(); + + /* + * Because the timer thread and the chip conversion clock + * are asynchronous, the period difference will eventually + * result in reading V[k-1] again, or skip V[k] at time Tk. + * In order to resync the timer with the conversion process + * we check the ConVersionReadyFlag. + * On hardware that supports using the ALERT pin to toggle a + * GPIO a triggered buffer could be used instead. + * For now, we pay for that extra read of the ALERT register + */ + if (!chip->allow_async_readout) + do { + ret = regmap_read(chip->regmap, INA226_ALERT_MASK, + &alert); + if (ret < 0) + return ret; + + alert &= INA266_CVRF; + } while (!alert); + + /* + * Single register reads: bulk_read will not work with ina226 + * as there is no auto-increment of the address register for + * data length longer than 16bits. + */ + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + unsigned int val; + + ret = regmap_read(chip->regmap, + INA2XX_SHUNT_VOLTAGE + bit, &val); + if (ret < 0) + return ret; + + data[i++] = val; + } + + time_b = iio_get_time_ns(); + + iio_push_to_buffers_with_timestamp(indio_dev, + (unsigned int *)data, time_a); + + chip->prev_ns = time_a; + + return (unsigned long)(time_b - time_a) / 1000; +}; + +static int ina2xx_capture_thread(void *data) +{ + struct iio_dev *indio_dev = data; + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + int buffer_us; + + /* + * Poll a bit faster than the chip internal Fs, in case + * we wish to sync with the conversion ready flag. + */ + if (!chip->allow_async_readout) + sampling_us -= 200; + + do { + buffer_us = ina2xx_work_buffer(indio_dev); + if (buffer_us < 0) + return buffer_us; + + if (sampling_us > buffer_us) + udelay(sampling_us - buffer_us); + + } while (!kthread_should_stop()); + + return 0; +} + +static int ina2xx_buffer_enable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + unsigned int sampling_us = SAMPLING_PERIOD(chip); + + dev_dbg(&indio_dev->dev, "Enabling buffer w/ scan_mask %02x, freq = %d, avg =%u\n", + (unsigned int)(*indio_dev->active_scan_mask), + 1000000 / sampling_us, chip->avg); + + dev_dbg(&indio_dev->dev, "Expected work period: %u us\n", sampling_us); + dev_dbg(&indio_dev->dev, "Async readout mode: %d\n", + chip->allow_async_readout); + + chip->prev_ns = iio_get_time_ns(); + + chip->task = kthread_run(ina2xx_capture_thread, (void *)indio_dev, + "%s:%d-%uus", indio_dev->name, indio_dev->id, + sampling_us); + + return PTR_ERR_OR_ZERO(chip->task); +} + +static int ina2xx_buffer_disable(struct iio_dev *indio_dev) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (chip->task) { + kthread_stop(chip->task); + chip->task = NULL; + } + + return 0; +} + +static const struct iio_buffer_setup_ops ina2xx_setup_ops = { + .postenable = &ina2xx_buffer_enable, + .predisable = &ina2xx_buffer_disable, +}; + +static int ina2xx_debug_reg(struct iio_dev *indio_dev, + unsigned reg, unsigned writeval, unsigned *readval) +{ + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + if (!readval) + return regmap_write(chip->regmap, reg, writeval); + + return regmap_read(chip->regmap, reg, readval); +} + +/* Possible integration times for vshunt and vbus */ +static IIO_CONST_ATTR_INT_TIME_AVAIL("0.000140 0.000204 0.000332 0.000588 0.001100 0.002116 0.004156 0.008244"); + +static IIO_DEVICE_ATTR(in_allow_async_readout, S_IRUGO | S_IWUSR, + ina2xx_allow_async_readout_show, + ina2xx_allow_async_readout_store, 0); + +static IIO_DEVICE_ATTR(in_shunt_resistor, S_IRUGO | S_IWUSR, + ina2xx_shunt_resistor_show, + ina2xx_shunt_resistor_store, 0); + +static struct attribute *ina2xx_attributes[] = { + &iio_dev_attr_in_allow_async_readout.dev_attr.attr, + &iio_const_attr_integration_time_available.dev_attr.attr, + &iio_dev_attr_in_shunt_resistor.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina2xx_attribute_group = { + .attrs = ina2xx_attributes, +}; + +static const struct iio_info ina2xx_info = { + .driver_module = THIS_MODULE, + .attrs = &ina2xx_attribute_group, + .read_raw = ina2xx_read_raw, + .write_raw = ina2xx_write_raw, + .debugfs_reg_access = ina2xx_debug_reg, +}; + +/* Initialize the configuration and calibration registers. */ +static int ina2xx_init(struct ina2xx_chip_info *chip, unsigned int config) +{ + int ret = regmap_write(chip->regmap, INA2XX_CONFIG, config); + if (ret) + return ret; + + return ina2xx_set_calibration(chip); +} + +static int ina2xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ina2xx_chip_info *chip; + struct iio_dev *indio_dev; + struct iio_buffer *buffer; + unsigned int val; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + /* This is only used for device removal purposes. */ + i2c_set_clientdata(client, indio_dev); + + chip->regmap = devm_regmap_init_i2c(client, &ina2xx_regmap_config); + if (IS_ERR(chip->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + return PTR_ERR(chip->regmap); + } + + chip->config = &ina2xx_config[id->driver_data]; + + mutex_init(&chip->state_lock); + + if (of_property_read_u32(client->dev.of_node, + "shunt-resistor", &val) < 0) { + struct ina2xx_platform_data *pdata = + dev_get_platdata(&client->dev); + + if (pdata) + val = pdata->shunt_uohms; + else + val = INA2XX_RSHUNT_DEFAULT; + } + + ret = set_shunt_resistor(chip, val); + if (ret) + return ret; + + /* Patch the current config register with default. */ + val = chip->config->config_default; + + if (id->driver_data == ina226) { + ina226_set_average(chip, INA226_DEFAULT_AVG, &val); + ina226_set_int_time_vbus(chip, INA226_DEFAULT_IT, &val); + ina226_set_int_time_vshunt(chip, INA226_DEFAULT_IT, &val); + } + + ret = ina2xx_init(chip, val); + if (ret) { + dev_err(&client->dev, "error configuring the device\n"); + return ret; + } + + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + indio_dev->dev.parent = &client->dev; + indio_dev->channels = ina2xx_channels; + indio_dev->num_channels = ARRAY_SIZE(ina2xx_channels); + indio_dev->name = id->name; + indio_dev->info = &ina2xx_info; + indio_dev->setup_ops = &ina2xx_setup_ops; + + buffer = devm_iio_kfifo_allocate(&indio_dev->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + return iio_device_register(indio_dev); +} + +static int ina2xx_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ina2xx_chip_info *chip = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + /* Powerdown */ + return regmap_update_bits(chip->regmap, INA2XX_CONFIG, + INA2XX_MODE_MASK, 0); +} + +static const struct i2c_device_id ina2xx_id[] = { + {"ina219", ina219}, + {"ina220", ina219}, + {"ina226", ina226}, + {"ina230", ina226}, + {"ina231", ina226}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = ina2xx_probe, + .remove = ina2xx_remove, + .id_table = ina2xx_id, +}; +module_i2c_driver(ina2xx_driver); + +MODULE_AUTHOR("Marc Titinger "); +MODULE_DESCRIPTION("Texas Instruments INA2XX ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/lpc18xx_adc.c b/drivers/iio/adc/lpc18xx_adc.c new file mode 100644 index 0000000..3ef18f4 --- /dev/null +++ b/drivers/iio/adc/lpc18xx_adc.c @@ -0,0 +1,231 @@ +/* + * IIO ADC driver for NXP LPC18xx ADC + * + * Copyright (C) 2016 Joachim Eastwood + * + * 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. + * + * UNSUPPORTED hardware features: + * - Hardware triggers + * - Burst mode + * - Interrupts + * - DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LPC18XX ADC registers and bits */ +#define LPC18XX_ADC_CR 0x000 +#define LPC18XX_ADC_CR_CLKDIV_SHIFT 8 +#define LPC18XX_ADC_CR_PDN BIT(21) +#define LPC18XX_ADC_CR_START_NOW (0x1 << 24) +#define LPC18XX_ADC_GDR 0x004 + +/* Data register bits */ +#define LPC18XX_ADC_SAMPLE_SHIFT 6 +#define LPC18XX_ADC_SAMPLE_MASK 0x3ff +#define LPC18XX_ADC_CONV_DONE BIT(31) + +/* Clock should be 4.5 MHz or less */ +#define LPC18XX_ADC_CLK_TARGET 4500000 + +struct lpc18xx_adc { + struct regulator *vref; + void __iomem *base; + struct device *dev; + struct mutex lock; + struct clk *clk; + u32 cr_reg; +}; + +#define LPC18XX_ADC_CHAN(_idx) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _idx, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec lpc18xx_adc_iio_channels[] = { + LPC18XX_ADC_CHAN(0), + LPC18XX_ADC_CHAN(1), + LPC18XX_ADC_CHAN(2), + LPC18XX_ADC_CHAN(3), + LPC18XX_ADC_CHAN(4), + LPC18XX_ADC_CHAN(5), + LPC18XX_ADC_CHAN(6), + LPC18XX_ADC_CHAN(7), +}; + +static int lpc18xx_adc_read_chan(struct lpc18xx_adc *adc, unsigned int ch) +{ + int ret; + u32 reg; + + reg = adc->cr_reg | BIT(ch) | LPC18XX_ADC_CR_START_NOW; + writel(reg, adc->base + LPC18XX_ADC_CR); + + ret = readl_poll_timeout(adc->base + LPC18XX_ADC_GDR, reg, + reg & LPC18XX_ADC_CONV_DONE, 3, 9); + if (ret) { + dev_warn(adc->dev, "adc read timed out\n"); + return ret; + } + + return (reg >> LPC18XX_ADC_SAMPLE_SHIFT) & LPC18XX_ADC_SAMPLE_MASK; +} + +static int lpc18xx_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lpc18xx_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *val = lpc18xx_adc_read_chan(adc, chan->channel); + mutex_unlock(&adc->lock); + if (*val < 0) + return *val; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(adc->vref) / 1000; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info lpc18xx_adc_info = { + .read_raw = lpc18xx_adc_read_raw, + .driver_module = THIS_MODULE, +}; + +static int lpc18xx_adc_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct lpc18xx_adc *adc; + struct resource *res; + unsigned int clkdiv; + unsigned long rate; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + adc = iio_priv(indio_dev); + adc->dev = &pdev->dev; + mutex_init(&adc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + adc->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + adc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(adc->clk)) { + dev_err(&pdev->dev, "error getting clock\n"); + return PTR_ERR(adc->clk); + } + + rate = clk_get_rate(adc->clk); + clkdiv = DIV_ROUND_UP(rate, LPC18XX_ADC_CLK_TARGET); + + adc->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(adc->vref)) { + dev_err(&pdev->dev, "error getting regulator\n"); + return PTR_ERR(adc->vref); + } + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &lpc18xx_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lpc18xx_adc_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(lpc18xx_adc_iio_channels); + + ret = regulator_enable(adc->vref); + if (ret) { + dev_err(&pdev->dev, "unable to enable regulator\n"); + return ret; + } + + ret = clk_prepare_enable(adc->clk); + if (ret) { + dev_err(&pdev->dev, "unable to enable clock\n"); + goto dis_reg; + } + + adc->cr_reg = (clkdiv << LPC18XX_ADC_CR_CLKDIV_SHIFT) | + LPC18XX_ADC_CR_PDN; + writel(adc->cr_reg, adc->base + LPC18XX_ADC_CR); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "unable to register device\n"); + goto dis_clk; + } + + return 0; + +dis_clk: + writel(0, adc->base + LPC18XX_ADC_CR); + clk_disable_unprepare(adc->clk); +dis_reg: + regulator_disable(adc->vref); + return ret; +} + +static int lpc18xx_adc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lpc18xx_adc *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + writel(0, adc->base + LPC18XX_ADC_CR); + clk_disable_unprepare(adc->clk); + regulator_disable(adc->vref); + + return 0; +} + +static const struct of_device_id lpc18xx_adc_match[] = { + { .compatible = "nxp,lpc1850-adc" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpc18xx_adc_match); + +static struct platform_driver lpc18xx_adc_driver = { + .probe = lpc18xx_adc_probe, + .remove = lpc18xx_adc_remove, + .driver = { + .name = "lpc18xx-adc", + .of_match_table = lpc18xx_adc_match, + }, +}; +module_platform_driver(lpc18xx_adc_driver); + +MODULE_DESCRIPTION("LPC18xx ADC driver"); +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/max1363.c b/drivers/iio/adc/max1363.c index 929508e..998dc3c 100644 --- a/drivers/iio/adc/max1363.c +++ b/drivers/iio/adc/max1363.c @@ -1386,7 +1386,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = { }, [max11644] = { .bits = 12, - .int_vref_mv = 2048, + .int_vref_mv = 4096, .mode_list = max11644_mode_list, .num_modes = ARRAY_SIZE(max11644_mode_list), .default_mode = s0to1, @@ -1396,7 +1396,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = { }, [max11645] = { .bits = 12, - .int_vref_mv = 4096, + .int_vref_mv = 2048, .mode_list = max11644_mode_list, .num_modes = ARRAY_SIZE(max11644_mode_list), .default_mode = s0to1, @@ -1406,7 +1406,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = { }, [max11646] = { .bits = 10, - .int_vref_mv = 2048, + .int_vref_mv = 4096, .mode_list = max11644_mode_list, .num_modes = ARRAY_SIZE(max11644_mode_list), .default_mode = s0to1, @@ -1416,7 +1416,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = { }, [max11647] = { .bits = 10, - .int_vref_mv = 4096, + .int_vref_mv = 2048, .mode_list = max11644_mode_list, .num_modes = ARRAY_SIZE(max11644_mode_list), .default_mode = s0to1, @@ -1680,6 +1680,10 @@ static const struct i2c_device_id max1363_id[] = { { "max11615", max11615 }, { "max11616", max11616 }, { "max11617", max11617 }, + { "max11644", max11644 }, + { "max11645", max11645 }, + { "max11646", max11646 }, + { "max11647", max11647 }, {} }; diff --git a/drivers/iio/adc/mcp320x.c b/drivers/iio/adc/mcp320x.c index 8569c8e..a850ca7 100644 --- a/drivers/iio/adc/mcp320x.c +++ b/drivers/iio/adc/mcp320x.c @@ -187,26 +187,27 @@ out: .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ } -#define MCP320X_VOLTAGE_CHANNEL_DIFF(num) \ +#define MCP320X_VOLTAGE_CHANNEL_DIFF(chan1, chan2) \ { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ - .channel = (num * 2), \ - .channel2 = (num * 2 + 1), \ - .address = (num * 2), \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .address = (chan1), \ .differential = 1, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ } static const struct iio_chan_spec mcp3201_channels[] = { - MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), }; static const struct iio_chan_spec mcp3202_channels[] = { MCP320X_VOLTAGE_CHANNEL(0), MCP320X_VOLTAGE_CHANNEL(1), - MCP320X_VOLTAGE_CHANNEL_DIFF(0), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), }; static const struct iio_chan_spec mcp3204_channels[] = { @@ -214,8 +215,10 @@ static const struct iio_chan_spec mcp3204_channels[] = { MCP320X_VOLTAGE_CHANNEL(1), MCP320X_VOLTAGE_CHANNEL(2), MCP320X_VOLTAGE_CHANNEL(3), - MCP320X_VOLTAGE_CHANNEL_DIFF(0), - MCP320X_VOLTAGE_CHANNEL_DIFF(1), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), + MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3), + MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2), }; static const struct iio_chan_spec mcp3208_channels[] = { @@ -227,10 +230,14 @@ static const struct iio_chan_spec mcp3208_channels[] = { MCP320X_VOLTAGE_CHANNEL(5), MCP320X_VOLTAGE_CHANNEL(6), MCP320X_VOLTAGE_CHANNEL(7), - MCP320X_VOLTAGE_CHANNEL_DIFF(0), - MCP320X_VOLTAGE_CHANNEL_DIFF(1), - MCP320X_VOLTAGE_CHANNEL_DIFF(2), - MCP320X_VOLTAGE_CHANNEL_DIFF(3), + MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1), + MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0), + MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3), + MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2), + MCP320X_VOLTAGE_CHANNEL_DIFF(4, 5), + MCP320X_VOLTAGE_CHANNEL_DIFF(5, 4), + MCP320X_VOLTAGE_CHANNEL_DIFF(6, 7), + MCP320X_VOLTAGE_CHANNEL_DIFF(7, 6), }; static const struct iio_info mcp320x_info = { @@ -354,6 +361,7 @@ static int mcp320x_remove(struct spi_device *spi) #if defined(CONFIG_OF) static const struct of_device_id mcp320x_dt_ids[] = { + /* NOTE: The use of compatibles with no vendor prefix is deprecated. */ { .compatible = "mcp3001", .data = &mcp320x_chip_infos[mcp3001], @@ -382,6 +390,33 @@ static const struct of_device_id mcp320x_dt_ids[] = { .compatible = "mcp3301", .data = &mcp320x_chip_infos[mcp3301], }, { + .compatible = "microchip,mcp3001", + .data = &mcp320x_chip_infos[mcp3001], + }, { + .compatible = "microchip,mcp3002", + .data = &mcp320x_chip_infos[mcp3002], + }, { + .compatible = "microchip,mcp3004", + .data = &mcp320x_chip_infos[mcp3004], + }, { + .compatible = "microchip,mcp3008", + .data = &mcp320x_chip_infos[mcp3008], + }, { + .compatible = "microchip,mcp3201", + .data = &mcp320x_chip_infos[mcp3201], + }, { + .compatible = "microchip,mcp3202", + .data = &mcp320x_chip_infos[mcp3202], + }, { + .compatible = "microchip,mcp3204", + .data = &mcp320x_chip_infos[mcp3204], + }, { + .compatible = "microchip,mcp3208", + .data = &mcp320x_chip_infos[mcp3208], + }, { + .compatible = "microchip,mcp3301", + .data = &mcp320x_chip_infos[mcp3301], + }, { } }; MODULE_DEVICE_TABLE(of, mcp320x_dt_ids); diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c index 3555122..d1172dc 100644 --- a/drivers/iio/adc/mcp3422.c +++ b/drivers/iio/adc/mcp3422.c @@ -1,11 +1,12 @@ /* - * mcp3422.c - driver for the Microchip mcp3422/3/4/6/7/8 chip family + * mcp3422.c - driver for the Microchip mcp3421/2/3/4/5/6/7/8 chip family * * Copyright (C) 2013, Angelo Compagnucci * Author: Angelo Compagnucci * * Datasheet: http://ww1.microchip.com/downloads/en/devicedoc/22088b.pdf * http://ww1.microchip.com/downloads/en/DeviceDoc/22226a.pdf + * http://ww1.microchip.com/downloads/en/DeviceDoc/22072b.pdf * * This driver exports the value of analog input voltage to sysfs, the * voltage unit is nV. @@ -60,9 +61,9 @@ static const int mcp3422_scales[4][4] = { { 1000000, 500000, 250000, 125000 }, - { 250000 , 125000, 62500 , 31250 }, - { 62500 , 31250 , 15625 , 7812 }, - { 15625 , 7812 , 3906 , 1953 } }; + { 250000, 125000, 62500, 31250 }, + { 62500, 31250, 15625, 7812 }, + { 15625, 7812, 3906, 1953 } }; /* Constant msleep times for data acquisitions */ static const int mcp3422_read_times[4] = { @@ -305,6 +306,10 @@ static const struct attribute_group mcp3422_attribute_group = { .attrs = mcp3422_attributes, }; +static const struct iio_chan_spec mcp3421_channels[] = { + MCP3422_CHAN(0), +}; + static const struct iio_chan_spec mcp3422_channels[] = { MCP3422_CHAN(0), MCP3422_CHAN(1), @@ -334,7 +339,7 @@ static int mcp3422_probe(struct i2c_client *client, u8 config; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*adc)); if (!indio_dev) @@ -352,6 +357,11 @@ static int mcp3422_probe(struct i2c_client *client, indio_dev->info = &mcp3422_info; switch (adc->id) { + case 1: + case 5: + indio_dev->channels = mcp3421_channels; + indio_dev->num_channels = ARRAY_SIZE(mcp3421_channels); + break; case 2: case 3: case 6: @@ -383,9 +393,11 @@ static int mcp3422_probe(struct i2c_client *client, } static const struct i2c_device_id mcp3422_id[] = { + { "mcp3421", 1 }, { "mcp3422", 2 }, { "mcp3423", 3 }, { "mcp3424", 4 }, + { "mcp3425", 5 }, { "mcp3426", 6 }, { "mcp3427", 7 }, { "mcp3428", 8 }, @@ -412,5 +424,5 @@ static struct i2c_driver mcp3422_driver = { module_i2c_driver(mcp3422_driver); MODULE_AUTHOR("Angelo Compagnucci "); -MODULE_DESCRIPTION("Microchip mcp3422/3/4/6/7/8 driver"); +MODULE_DESCRIPTION("Microchip mcp3421/2/3/4/5/6/7/8 driver"); MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/mxs-lradc.c b/drivers/iio/adc/mxs-lradc.c new file mode 100644 index 0000000..ad26da1 --- /dev/null +++ b/drivers/iio/adc/mxs-lradc.c @@ -0,0 +1,1772 @@ +/* + * Freescale MXS LRADC driver + * + * Copyright (c) 2012 DENX Software Engineering, GmbH. + * Marek Vasut + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxs-lradc" + +#define LRADC_MAX_DELAY_CHANS 4 +#define LRADC_MAX_MAPPED_CHANS 8 +#define LRADC_MAX_TOTAL_CHANS 16 + +#define LRADC_DELAY_TIMER_HZ 2000 + +/* + * Make this runtime configurable if necessary. Currently, if the buffered mode + * is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before + * triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000) + * seconds. The result is that the samples arrive every 500mS. + */ +#define LRADC_DELAY_TIMER_PER 200 +#define LRADC_DELAY_TIMER_LOOP 5 + +/* + * Once the pen touches the touchscreen, the touchscreen switches from + * IRQ-driven mode to polling mode to prevent interrupt storm. The polling + * is realized by worker thread, which is called every 20 or so milliseconds. + * This gives the touchscreen enough fluency and does not strain the system + * too much. + */ +#define LRADC_TS_SAMPLE_DELAY_MS 5 + +/* + * The LRADC reads the following amount of samples from each touchscreen + * channel and the driver then computes average of these. + */ +#define LRADC_TS_SAMPLE_AMOUNT 4 + +enum mxs_lradc_id { + IMX23_LRADC, + IMX28_LRADC, +}; + +static const char * const mx23_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", +}; + +static const char * const mx28_lradc_irq_names[] = { + "mxs-lradc-touchscreen", + "mxs-lradc-thresh0", + "mxs-lradc-thresh1", + "mxs-lradc-channel0", + "mxs-lradc-channel1", + "mxs-lradc-channel2", + "mxs-lradc-channel3", + "mxs-lradc-channel4", + "mxs-lradc-channel5", + "mxs-lradc-channel6", + "mxs-lradc-channel7", + "mxs-lradc-button0", + "mxs-lradc-button1", +}; + +struct mxs_lradc_of_config { + const int irq_count; + const char * const *irq_name; + const u32 *vref_mv; +}; + +#define VREF_MV_BASE 1850 + +static const u32 mx23_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE * 2, /* CH6 VDDIO */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE, /* CH10 */ + VREF_MV_BASE, /* CH11 */ + VREF_MV_BASE, /* CH12 USB_DP */ + VREF_MV_BASE, /* CH13 USB_DN */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const u32 mx28_vref_mv[LRADC_MAX_TOTAL_CHANS] = { + VREF_MV_BASE, /* CH0 */ + VREF_MV_BASE, /* CH1 */ + VREF_MV_BASE, /* CH2 */ + VREF_MV_BASE, /* CH3 */ + VREF_MV_BASE, /* CH4 */ + VREF_MV_BASE, /* CH5 */ + VREF_MV_BASE, /* CH6 */ + VREF_MV_BASE * 4, /* CH7 VBATT */ + VREF_MV_BASE, /* CH8 Temp sense 0 */ + VREF_MV_BASE, /* CH9 Temp sense 1 */ + VREF_MV_BASE * 2, /* CH10 VDDIO */ + VREF_MV_BASE, /* CH11 VTH */ + VREF_MV_BASE * 2, /* CH12 VDDA */ + VREF_MV_BASE, /* CH13 VDDD */ + VREF_MV_BASE, /* CH14 VBG */ + VREF_MV_BASE * 4, /* CH15 VDD5V */ +}; + +static const struct mxs_lradc_of_config mxs_lradc_of_config[] = { + [IMX23_LRADC] = { + .irq_count = ARRAY_SIZE(mx23_lradc_irq_names), + .irq_name = mx23_lradc_irq_names, + .vref_mv = mx23_vref_mv, + }, + [IMX28_LRADC] = { + .irq_count = ARRAY_SIZE(mx28_lradc_irq_names), + .irq_name = mx28_lradc_irq_names, + .vref_mv = mx28_vref_mv, + }, +}; + +enum mxs_lradc_ts { + MXS_LRADC_TOUCHSCREEN_NONE = 0, + MXS_LRADC_TOUCHSCREEN_4WIRE, + MXS_LRADC_TOUCHSCREEN_5WIRE, +}; + +/* + * Touchscreen handling + */ +enum lradc_ts_plate { + LRADC_TOUCH = 0, + LRADC_SAMPLE_X, + LRADC_SAMPLE_Y, + LRADC_SAMPLE_PRESSURE, + LRADC_SAMPLE_VALID, +}; + +enum mxs_lradc_divbytwo { + MXS_LRADC_DIV_DISABLED = 0, + MXS_LRADC_DIV_ENABLED, +}; + +struct mxs_lradc_scale { + unsigned int integer; + unsigned int nano; +}; + +struct mxs_lradc { + struct device *dev; + void __iomem *base; + int irq[13]; + + struct clk *clk; + + u32 *buffer; + struct iio_trigger *trig; + + struct mutex lock; + + struct completion completion; + + const u32 *vref_mv; + struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2]; + unsigned long is_divided; + + /* + * When the touchscreen is enabled, we give it two private virtual + * channels: #6 and #7. This means that only 6 virtual channels (instead + * of 8) will be available for buffered capture. + */ +#define TOUCHSCREEN_VCHANNEL1 7 +#define TOUCHSCREEN_VCHANNEL2 6 +#define BUFFER_VCHANS_LIMITED 0x3f +#define BUFFER_VCHANS_ALL 0xff + u8 buffer_vchans; + + /* + * Furthermore, certain LRADC channels are shared between touchscreen + * and/or touch-buttons and generic LRADC block. Therefore when using + * either of these, these channels are not available for the regular + * sampling. The shared channels are as follows: + * + * CH0 -- Touch button #0 + * CH1 -- Touch button #1 + * CH2 -- Touch screen XPUL + * CH3 -- Touch screen YPLL + * CH4 -- Touch screen XNUL + * CH5 -- Touch screen YNLR + * CH6 -- Touch screen WIPER (5-wire only) + * + * The bit fields below represents which parts of the LRADC block are + * switched into special mode of operation. These channels can not + * be sampled as regular LRADC channels. The driver will refuse any + * attempt to sample these channels. + */ +#define CHAN_MASK_TOUCHBUTTON (BIT(1) | BIT(0)) +#define CHAN_MASK_TOUCHSCREEN_4WIRE (0xf << 2) +#define CHAN_MASK_TOUCHSCREEN_5WIRE (0x1f << 2) + enum mxs_lradc_ts use_touchscreen; + bool use_touchbutton; + + struct input_dev *ts_input; + + enum mxs_lradc_id soc; + enum lradc_ts_plate cur_plate; /* state machine */ + bool ts_valid; + unsigned ts_x_pos; + unsigned ts_y_pos; + unsigned ts_pressure; + + /* handle touchscreen's physical behaviour */ + /* samples per coordinate */ + unsigned over_sample_cnt; + /* time clocks between samples */ + unsigned over_sample_delay; + /* time in clocks to wait after the plates where switched */ + unsigned settling_delay; +}; + +#define LRADC_CTRL0 0x00 +# define LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE BIT(23) +# define LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE BIT(22) +# define LRADC_CTRL0_MX28_YNNSW /* YM */ BIT(21) +# define LRADC_CTRL0_MX28_YPNSW /* YP */ BIT(20) +# define LRADC_CTRL0_MX28_YPPSW /* YP */ BIT(19) +# define LRADC_CTRL0_MX28_XNNSW /* XM */ BIT(18) +# define LRADC_CTRL0_MX28_XNPSW /* XM */ BIT(17) +# define LRADC_CTRL0_MX28_XPPSW /* XP */ BIT(16) + +# define LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE BIT(20) +# define LRADC_CTRL0_MX23_YM BIT(19) +# define LRADC_CTRL0_MX23_XM BIT(18) +# define LRADC_CTRL0_MX23_YP BIT(17) +# define LRADC_CTRL0_MX23_XP BIT(16) + +# define LRADC_CTRL0_MX28_PLATE_MASK \ + (LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX28_YNNSW | LRADC_CTRL0_MX28_YPNSW | \ + LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW | \ + LRADC_CTRL0_MX28_XNPSW | LRADC_CTRL0_MX28_XPPSW) + +# define LRADC_CTRL0_MX23_PLATE_MASK \ + (LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE | \ + LRADC_CTRL0_MX23_YM | LRADC_CTRL0_MX23_XM | \ + LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XP) + +#define LRADC_CTRL1 0x10 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ_EN BIT(24) +#define LRADC_CTRL1_LRADC_IRQ_EN(n) (1 << ((n) + 16)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK (0x1fff << 16) +#define LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK (0x01ff << 16) +#define LRADC_CTRL1_LRADC_IRQ_EN_OFFSET 16 +#define LRADC_CTRL1_TOUCH_DETECT_IRQ BIT(8) +#define LRADC_CTRL1_LRADC_IRQ(n) (1 << (n)) +#define LRADC_CTRL1_MX28_LRADC_IRQ_MASK 0x1fff +#define LRADC_CTRL1_MX23_LRADC_IRQ_MASK 0x01ff +#define LRADC_CTRL1_LRADC_IRQ_OFFSET 0 + +#define LRADC_CTRL2 0x20 +#define LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET 24 +#define LRADC_CTRL2_TEMPSENSE_PWD BIT(15) + +#define LRADC_STATUS 0x40 +#define LRADC_STATUS_TOUCH_DETECT_RAW BIT(0) + +#define LRADC_CH(n) (0x50 + (0x10 * (n))) +#define LRADC_CH_ACCUMULATE BIT(29) +#define LRADC_CH_NUM_SAMPLES_MASK (0x1f << 24) +#define LRADC_CH_NUM_SAMPLES_OFFSET 24 +#define LRADC_CH_NUM_SAMPLES(x) \ + ((x) << LRADC_CH_NUM_SAMPLES_OFFSET) +#define LRADC_CH_VALUE_MASK 0x3ffff +#define LRADC_CH_VALUE_OFFSET 0 + +#define LRADC_DELAY(n) (0xd0 + (0x10 * (n))) +#define LRADC_DELAY_TRIGGER_LRADCS_MASK (0xffUL << 24) +#define LRADC_DELAY_TRIGGER_LRADCS_OFFSET 24 +#define LRADC_DELAY_TRIGGER(x) \ + (((x) << LRADC_DELAY_TRIGGER_LRADCS_OFFSET) & \ + LRADC_DELAY_TRIGGER_LRADCS_MASK) +#define LRADC_DELAY_KICK BIT(20) +#define LRADC_DELAY_TRIGGER_DELAYS_MASK (0xf << 16) +#define LRADC_DELAY_TRIGGER_DELAYS_OFFSET 16 +#define LRADC_DELAY_TRIGGER_DELAYS(x) \ + (((x) << LRADC_DELAY_TRIGGER_DELAYS_OFFSET) & \ + LRADC_DELAY_TRIGGER_DELAYS_MASK) +#define LRADC_DELAY_LOOP_COUNT_MASK (0x1f << 11) +#define LRADC_DELAY_LOOP_COUNT_OFFSET 11 +#define LRADC_DELAY_LOOP(x) \ + (((x) << LRADC_DELAY_LOOP_COUNT_OFFSET) & \ + LRADC_DELAY_LOOP_COUNT_MASK) +#define LRADC_DELAY_DELAY_MASK 0x7ff +#define LRADC_DELAY_DELAY_OFFSET 0 +#define LRADC_DELAY_DELAY(x) \ + (((x) << LRADC_DELAY_DELAY_OFFSET) & \ + LRADC_DELAY_DELAY_MASK) + +#define LRADC_CTRL4 0x140 +#define LRADC_CTRL4_LRADCSELECT_MASK(n) (0xf << ((n) * 4)) +#define LRADC_CTRL4_LRADCSELECT_OFFSET(n) ((n) * 4) +#define LRADC_CTRL4_LRADCSELECT(n, x) \ + (((x) << LRADC_CTRL4_LRADCSELECT_OFFSET(n)) & \ + LRADC_CTRL4_LRADCSELECT_MASK(n)) + +#define LRADC_RESOLUTION 12 +#define LRADC_SINGLE_SAMPLE_MASK ((1 << LRADC_RESOLUTION) - 1) + +static void mxs_lradc_reg_set(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_SET); +} + +static void mxs_lradc_reg_clear(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg + STMP_OFFSET_REG_CLR); +} + +static void mxs_lradc_reg_wrt(struct mxs_lradc *lradc, u32 val, u32 reg) +{ + writel(val, lradc->base + reg); +} + +static u32 mxs_lradc_plate_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_PLATE_MASK; + return LRADC_CTRL0_MX28_PLATE_MASK; +} + +static u32 mxs_lradc_irq_en_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_EN_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_EN_MASK; +} + +static u32 mxs_lradc_irq_mask(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL1_MX23_LRADC_IRQ_MASK; + return LRADC_CTRL1_MX28_LRADC_IRQ_MASK; +} + +static u32 mxs_lradc_touch_detect_bit(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_TOUCH_DETECT_ENABLE; + return LRADC_CTRL0_MX28_TOUCH_DETECT_ENABLE; +} + +static u32 mxs_lradc_drive_x_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_XP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_XPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static u32 mxs_lradc_drive_y_plate(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_YM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_YNNSW; +} + +static u32 mxs_lradc_drive_pressure(struct mxs_lradc *lradc) +{ + if (lradc->soc == IMX23_LRADC) + return LRADC_CTRL0_MX23_YP | LRADC_CTRL0_MX23_XM; + return LRADC_CTRL0_MX28_YPPSW | LRADC_CTRL0_MX28_XNNSW; +} + +static bool mxs_lradc_check_touch_event(struct mxs_lradc *lradc) +{ + return !!(readl(lradc->base + LRADC_STATUS) & + LRADC_STATUS_TOUCH_DETECT_RAW); +} + +static void mxs_lradc_map_channel(struct mxs_lradc *lradc, unsigned vch, + unsigned ch) +{ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(vch), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, LRADC_CTRL4_LRADCSELECT(vch, ch), LRADC_CTRL4); +} + +static void mxs_lradc_setup_ts_channel(struct mxs_lradc *lradc, unsigned ch) +{ + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + mxs_lradc_reg_wrt(lradc, LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1), + LRADC_CH(ch)); + + /* + * from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch)); + + /* + * prepare the delay/loop unit according to the oversampling count + * + * from the datasheet: + * "The DELAY fields in HW_LRADC_DELAY0, HW_LRADC_DELAY1, + * HW_LRADC_DELAY2, and HW_LRADC_DELAY3 must be non-zero; otherwise, + * the LRADC will not trigger the delay group." + */ + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_TRIGGER(1 << ch) | + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt( + lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), + LRADC_DELAY(2)); +} + +/* + * Pressure detection is special: + * We want to do both required measurements for the pressure detection in + * one turn. Use the hardware features to chain both conversions and let the + * hardware report one interrupt if both conversions are done + */ +static void mxs_lradc_setup_ts_pressure(struct mxs_lradc *lradc, unsigned ch1, + unsigned ch2) +{ + u32 reg; + + /* + * prepare for oversampling conversion + * + * from the datasheet: + * "The ACCUMULATE bit in the appropriate channel register + * HW_LRADC_CHn must be set to 1 if NUM_SAMPLES is greater then 0; + * otherwise, the IRQs will not fire." + */ + reg = LRADC_CH_ACCUMULATE | + LRADC_CH_NUM_SAMPLES(lradc->over_sample_cnt - 1); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch1)); + mxs_lradc_reg_wrt(lradc, reg, LRADC_CH(ch2)); + + /* + * from the datasheet: + * "Software must clear this register in preparation for a + * multi-cycle accumulation. + */ + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch1)); + mxs_lradc_reg_clear(lradc, LRADC_CH_VALUE_MASK, LRADC_CH(ch2)); + + /* prepare the delay/loop unit according to the oversampling count */ + mxs_lradc_reg_wrt( + lradc, + LRADC_DELAY_TRIGGER(1 << ch1) | + LRADC_DELAY_TRIGGER(1 << ch2) | /* start both channels */ + LRADC_DELAY_TRIGGER_DELAYS(0) | + LRADC_DELAY_LOOP(lradc->over_sample_cnt - 1) | + LRADC_DELAY_DELAY(lradc->over_sample_delay - 1), + LRADC_DELAY(3)); + + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ(ch2), LRADC_CTRL1); + + /* + * after changing the touchscreen plates setting + * the signals need some initial time to settle. Start the + * SoC's delay unit and start the conversion later + * and automatically. + */ + mxs_lradc_reg_wrt( + lradc, + LRADC_DELAY_TRIGGER(0) | /* don't trigger ADC */ + LRADC_DELAY_TRIGGER_DELAYS(BIT(3)) | /* trigger DELAY unit#3 */ + LRADC_DELAY_KICK | + LRADC_DELAY_DELAY(lradc->settling_delay), LRADC_DELAY(2)); +} + +static unsigned mxs_lradc_read_raw_channel(struct mxs_lradc *lradc, + unsigned channel) +{ + u32 reg; + unsigned num_samples, val; + + reg = readl(lradc->base + LRADC_CH(channel)); + if (reg & LRADC_CH_ACCUMULATE) + num_samples = lradc->over_sample_cnt; + else + num_samples = 1; + + val = (reg & LRADC_CH_VALUE_MASK) >> LRADC_CH_VALUE_OFFSET; + return val / num_samples; +} + +static unsigned mxs_lradc_read_ts_pressure(struct mxs_lradc *lradc, + unsigned ch1, unsigned ch2) +{ + u32 reg, mask; + unsigned pressure, m1, m2; + + mask = LRADC_CTRL1_LRADC_IRQ(ch1) | LRADC_CTRL1_LRADC_IRQ(ch2); + reg = readl(lradc->base + LRADC_CTRL1) & mask; + + while (reg != mask) { + reg = readl(lradc->base + LRADC_CTRL1) & mask; + dev_dbg(lradc->dev, "One channel is still busy: %X\n", reg); + } + + m1 = mxs_lradc_read_raw_channel(lradc, ch1); + m2 = mxs_lradc_read_raw_channel(lradc, ch2); + + if (m2 == 0) { + dev_warn(lradc->dev, "Cannot calculate pressure\n"); + return 1 << (LRADC_RESOLUTION - 1); + } + + /* simply scale the value from 0 ... max ADC resolution */ + pressure = m1; + pressure *= (1 << LRADC_RESOLUTION); + pressure /= m2; + + dev_dbg(lradc->dev, "Pressure = %u\n", pressure); + return pressure; +} + +#define TS_CH_XP 2 +#define TS_CH_YP 3 +#define TS_CH_XM 4 +#define TS_CH_YM 5 + +/* + * YP(open)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(weak+) XM(open) + * + * "weak+" means 200k Ohm VDDIO + * (-) means GND + */ +static void mxs_lradc_setup_touch_detection(struct mxs_lradc *lradc) +{ + /* + * In order to detect a touch event the 'touch detect enable' bit + * enables: + * - a weak pullup to the X+ connector + * - a strong ground at the Y- connector + */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_touch_detect_bit(lradc), + LRADC_CTRL0); +} + +/* + * YP(meas)--+-------------+ + * | |--+ + * | | | + * YM(open)--+-------------+ | + * +--------------+ + * | | + * XP(+) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_x_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_x_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_X; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YP); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(-)--+-------------+ | + * +--------------+ + * | | + * XP(open) XM(meas) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_y_pos(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_y_plate(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_Y; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_XM); + mxs_lradc_setup_ts_channel(lradc, TOUCHSCREEN_VCHANNEL1); +} + +/* + * YP(+)--+-------------+ + * | |--+ + * | | | + * YM(meas)--+-------------+ | + * +--------------+ + * | | + * XP(meas) XM(-) + * + * (+) means here 1.85 V + * (-) means here GND + */ +static void mxs_lradc_prepare_pressure(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); + mxs_lradc_reg_set(lradc, mxs_lradc_drive_pressure(lradc), LRADC_CTRL0); + + lradc->cur_plate = LRADC_SAMPLE_PRESSURE; + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL1, TS_CH_YM); + mxs_lradc_map_channel(lradc, TOUCHSCREEN_VCHANNEL2, TS_CH_XP); + mxs_lradc_setup_ts_pressure(lradc, TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); +} + +static void mxs_lradc_enable_touch_detection(struct mxs_lradc *lradc) +{ + /* Configure the touchscreen type */ + if (lradc->soc == IMX28_LRADC) { + mxs_lradc_reg_clear(lradc, LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + mxs_lradc_reg_set(lradc, + LRADC_CTRL0_MX28_TOUCH_SCREEN_TYPE, + LRADC_CTRL0); + } + + mxs_lradc_setup_touch_detection(lradc); + + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +static void mxs_lradc_start_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_reg_clear(lradc, + LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, + LRADC_CTRL1); + mxs_lradc_reg_set(lradc, + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1), + LRADC_CTRL1); + /* + * start with the Y-pos, because it uses nearly the same plate + * settings like the touch detection + */ + mxs_lradc_prepare_y_pos(lradc); +} + +static void mxs_lradc_report_ts_event(struct mxs_lradc *lradc) +{ + input_report_abs(lradc->ts_input, ABS_X, lradc->ts_x_pos); + input_report_abs(lradc->ts_input, ABS_Y, lradc->ts_y_pos); + input_report_abs(lradc->ts_input, ABS_PRESSURE, lradc->ts_pressure); + input_report_key(lradc->ts_input, BTN_TOUCH, 1); + input_sync(lradc->ts_input); +} + +static void mxs_lradc_complete_touch_event(struct mxs_lradc *lradc) +{ + mxs_lradc_setup_touch_detection(lradc); + lradc->cur_plate = LRADC_SAMPLE_VALID; + /* + * start a dummy conversion to burn time to settle the signals + * note: we are not interested in the conversion's value + */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(TOUCHSCREEN_VCHANNEL1)); + mxs_lradc_reg_clear(lradc, + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2), + LRADC_CTRL1); + mxs_lradc_reg_wrt( + lradc, + LRADC_DELAY_TRIGGER(1 << TOUCHSCREEN_VCHANNEL1) | + LRADC_DELAY_KICK | LRADC_DELAY_DELAY(10), /* waste 5 ms */ + LRADC_DELAY(2)); +} + +/* + * in order to avoid false measurements, report only samples where + * the surface is still touched after the position measurement + */ +static void mxs_lradc_finish_touch_event(struct mxs_lradc *lradc, bool valid) +{ + /* if it is still touched, report the sample */ + if (valid && mxs_lradc_check_touch_event(lradc)) { + lradc->ts_valid = true; + mxs_lradc_report_ts_event(lradc); + } + + /* if it is even still touched, continue with the next measurement */ + if (mxs_lradc_check_touch_event(lradc)) { + mxs_lradc_prepare_y_pos(lradc); + return; + } + + if (lradc->ts_valid) { + /* signal the release */ + lradc->ts_valid = false; + input_report_key(lradc->ts_input, BTN_TOUCH, 0); + input_sync(lradc->ts_input); + } + + /* if it is released, wait for the next touch via IRQ */ + lradc->cur_plate = LRADC_TOUCH; + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + mxs_lradc_reg_clear(lradc, + LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1), + LRADC_CTRL1); + mxs_lradc_reg_set(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN, LRADC_CTRL1); +} + +/* touchscreen's state machine */ +static void mxs_lradc_handle_touch(struct mxs_lradc *lradc) +{ + switch (lradc->cur_plate) { + case LRADC_TOUCH: + if (mxs_lradc_check_touch_event(lradc)) + mxs_lradc_start_touch_event(lradc); + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ, + LRADC_CTRL1); + return; + + case LRADC_SAMPLE_Y: + lradc->ts_y_pos = + mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_x_pos(lradc); + return; + + case LRADC_SAMPLE_X: + lradc->ts_x_pos = + mxs_lradc_read_raw_channel(lradc, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_prepare_pressure(lradc); + return; + + case LRADC_SAMPLE_PRESSURE: + lradc->ts_pressure = + mxs_lradc_read_ts_pressure(lradc, + TOUCHSCREEN_VCHANNEL2, + TOUCHSCREEN_VCHANNEL1); + mxs_lradc_complete_touch_event(lradc); + return; + + case LRADC_SAMPLE_VALID: + mxs_lradc_finish_touch_event(lradc, 1); + break; + } +} + +/* + * Raw I/O operations + */ +static int mxs_lradc_read_single(struct iio_dev *iio_dev, int chan, int *val) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + int ret; + + /* + * See if there is no buffered operation in progress. If there is, simply + * bail out. This can be improved to support both buffered and raw IO at + * the same time, yet the code becomes horribly complicated. Therefore I + * applied KISS principle here. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + reinit_completion(&lradc->completion); + + /* + * No buffered operation in progress, map the channel and trigger it. + * Virtual channel 0 is always used here as the others are always not + * used if doing raw sampling. + */ + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, 0x1, LRADC_CTRL0); + + /* Enable / disable the divider per requirement */ + if (test_bit(chan, &lradc->is_divided)) + mxs_lradc_reg_set(lradc, + 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + LRADC_CTRL2); + else + mxs_lradc_reg_clear(lradc, + 1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET, + LRADC_CTRL2); + + /* Clean the slot's previous content, then set new one. */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL4_LRADCSELECT_MASK(0), + LRADC_CTRL4); + mxs_lradc_reg_set(lradc, chan, LRADC_CTRL4); + + mxs_lradc_reg_wrt(lradc, 0, LRADC_CH(0)); + + /* Enable the IRQ and start sampling the channel. */ + mxs_lradc_reg_set(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + mxs_lradc_reg_set(lradc, BIT(0), LRADC_CTRL0); + + /* Wait for completion on the channel, 1 second max. */ + ret = wait_for_completion_killable_timeout(&lradc->completion, HZ); + if (!ret) + ret = -ETIMEDOUT; + if (ret < 0) + goto err; + + /* Read the data. */ + *val = readl(lradc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK; + ret = IIO_VAL_INT; + +err: + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_LRADC_IRQ_EN(0), LRADC_CTRL1); + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_read_temp(struct iio_dev *iio_dev, int *val) +{ + int ret, min, max; + + ret = mxs_lradc_read_single(iio_dev, 8, &min); + if (ret != IIO_VAL_INT) + return ret; + + ret = mxs_lradc_read_single(iio_dev, 9, &max); + if (ret != IIO_VAL_INT) + return ret; + + *val = max - min; + + return IIO_VAL_INT; +} + +static int mxs_lradc_read_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + + switch (m) { + case IIO_CHAN_INFO_RAW: + if (chan->type == IIO_TEMP) + return mxs_lradc_read_temp(iio_dev, val); + + return mxs_lradc_read_single(iio_dev, chan->channel, val); + + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_TEMP) { + /* + * From the datasheet, we have to multiply by 1.012 and + * divide by 4 + */ + *val = 0; + *val2 = 253000; + return IIO_VAL_INT_PLUS_MICRO; + } + + *val = lradc->vref_mv[chan->channel]; + *val2 = chan->scan_type.realbits - + test_bit(chan->channel, &lradc->is_divided); + return IIO_VAL_FRACTIONAL_LOG2; + + case IIO_CHAN_INFO_OFFSET: + if (chan->type == IIO_TEMP) { + /* + * The calculated value from the ADC is in Kelvin, we + * want Celsius for hwmon so the offset is -273.15 + * The offset is applied before scaling so it is + * actually -213.15 * 4 / 1.012 = -1079.644268 + */ + *val = -1079; + *val2 = 644268; + + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; + + default: + break; + } + + return -EINVAL; +} + +static int mxs_lradc_write_raw(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long m) +{ + struct mxs_lradc *lradc = iio_priv(iio_dev); + struct mxs_lradc_scale *scale_avail = + lradc->scale_avail[chan->channel]; + int ret; + + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + switch (m) { + case IIO_CHAN_INFO_SCALE: + ret = -EINVAL; + if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) { + /* divider by two disabled */ + clear_bit(chan->channel, &lradc->is_divided); + ret = 0; + } else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer && + val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) { + /* divider by two enabled */ + set_bit(chan->channel, &lradc->is_divided); + ret = 0; + } + + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&lradc->lock); + + return ret; +} + +static int mxs_lradc_write_raw_get_fmt(struct iio_dev *iio_dev, + const struct iio_chan_spec *chan, + long m) +{ + return IIO_VAL_INT_PLUS_NANO; +} + +static ssize_t mxs_lradc_show_scale_available_ch(struct device *dev, + struct device_attribute *attr, + char *buf, + int ch) +{ + struct iio_dev *iio = dev_to_iio_dev(dev); + struct mxs_lradc *lradc = iio_priv(iio); + int i, len = 0; + + for (i = 0; i < ARRAY_SIZE(lradc->scale_avail[ch]); i++) + len += sprintf(buf + len, "%u.%09u ", + lradc->scale_avail[ch][i].integer, + lradc->scale_avail[ch][i].nano); + + len += sprintf(buf + len, "\n"); + + return len; +} + +static ssize_t mxs_lradc_show_scale_available(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr); + + return mxs_lradc_show_scale_available_ch(dev, attr, buf, + iio_attr->address); +} + +#define SHOW_SCALE_AVAILABLE_ATTR(ch) \ +static IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, S_IRUGO, \ + mxs_lradc_show_scale_available, NULL, ch) + +SHOW_SCALE_AVAILABLE_ATTR(0); +SHOW_SCALE_AVAILABLE_ATTR(1); +SHOW_SCALE_AVAILABLE_ATTR(2); +SHOW_SCALE_AVAILABLE_ATTR(3); +SHOW_SCALE_AVAILABLE_ATTR(4); +SHOW_SCALE_AVAILABLE_ATTR(5); +SHOW_SCALE_AVAILABLE_ATTR(6); +SHOW_SCALE_AVAILABLE_ATTR(7); +SHOW_SCALE_AVAILABLE_ATTR(10); +SHOW_SCALE_AVAILABLE_ATTR(11); +SHOW_SCALE_AVAILABLE_ATTR(12); +SHOW_SCALE_AVAILABLE_ATTR(13); +SHOW_SCALE_AVAILABLE_ATTR(14); +SHOW_SCALE_AVAILABLE_ATTR(15); + +static struct attribute *mxs_lradc_attributes[] = { + &iio_dev_attr_in_voltage0_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage1_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage2_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage3_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage4_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage5_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage6_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage7_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage10_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage11_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage12_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage13_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage14_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage15_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group mxs_lradc_attribute_group = { + .attrs = mxs_lradc_attributes, +}; + +static const struct iio_info mxs_lradc_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = mxs_lradc_read_raw, + .write_raw = mxs_lradc_write_raw, + .write_raw_get_fmt = mxs_lradc_write_raw_get_fmt, + .attrs = &mxs_lradc_attribute_group, +}; + +static int mxs_lradc_ts_open(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + /* Enable the touch-detect circuitry. */ + mxs_lradc_enable_touch_detection(lradc); + + return 0; +} + +static void mxs_lradc_disable_ts(struct mxs_lradc *lradc) +{ + /* stop all interrupts from firing */ + mxs_lradc_reg_clear(lradc, LRADC_CTRL1_TOUCH_DETECT_IRQ_EN | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ_EN(TOUCHSCREEN_VCHANNEL2), LRADC_CTRL1); + + /* Power-down touchscreen touch-detect circuitry. */ + mxs_lradc_reg_clear(lradc, mxs_lradc_plate_mask(lradc), LRADC_CTRL0); +} + +static void mxs_lradc_ts_close(struct input_dev *dev) +{ + struct mxs_lradc *lradc = input_get_drvdata(dev); + + mxs_lradc_disable_ts(lradc); +} + +static int mxs_lradc_ts_register(struct mxs_lradc *lradc) +{ + struct input_dev *input; + struct device *dev = lradc->dev; + int ret; + + if (!lradc->use_touchscreen) + return 0; + + input = input_allocate_device(); + if (!input) + return -ENOMEM; + + input->name = DRIVER_NAME; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + input->open = mxs_lradc_ts_open; + input->close = mxs_lradc_ts_close; + + __set_bit(EV_ABS, input->evbit); + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_TOUCH, input->keybit); + __set_bit(INPUT_PROP_DIRECT, input->propbit); + input_set_abs_params(input, ABS_X, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_Y, 0, LRADC_SINGLE_SAMPLE_MASK, 0, 0); + input_set_abs_params(input, ABS_PRESSURE, 0, LRADC_SINGLE_SAMPLE_MASK, + 0, 0); + + lradc->ts_input = input; + input_set_drvdata(input, lradc); + ret = input_register_device(input); + if (ret) + input_free_device(lradc->ts_input); + + return ret; +} + +static void mxs_lradc_ts_unregister(struct mxs_lradc *lradc) +{ + if (!lradc->use_touchscreen) + return; + + mxs_lradc_disable_ts(lradc); + input_unregister_device(lradc->ts_input); +} + +/* + * IRQ Handling + */ +static irqreturn_t mxs_lradc_handle_irq(int irq, void *data) +{ + struct iio_dev *iio = data; + struct mxs_lradc *lradc = iio_priv(iio); + unsigned long reg = readl(lradc->base + LRADC_CTRL1); + u32 clr_irq = mxs_lradc_irq_mask(lradc); + const u32 ts_irq_mask = + LRADC_CTRL1_TOUCH_DETECT_IRQ | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2); + + if (!(reg & mxs_lradc_irq_mask(lradc))) + return IRQ_NONE; + + if (lradc->use_touchscreen && (reg & ts_irq_mask)) { + mxs_lradc_handle_touch(lradc); + + /* Make sure we don't clear the next conversion's interrupt. */ + clr_irq &= ~(LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL1) | + LRADC_CTRL1_LRADC_IRQ(TOUCHSCREEN_VCHANNEL2)); + } + + if (iio_buffer_enabled(iio)) { + if (reg & lradc->buffer_vchans) + iio_trigger_poll(iio->trig); + } else if (reg & LRADC_CTRL1_LRADC_IRQ(0)) { + complete(&lradc->completion); + } + + mxs_lradc_reg_clear(lradc, reg & clr_irq, LRADC_CTRL1); + + return IRQ_HANDLED; +} + +/* + * Trigger handling + */ +static irqreturn_t mxs_lradc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *iio = pf->indio_dev; + struct mxs_lradc *lradc = iio_priv(iio); + const u32 chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + unsigned int i, j = 0; + + for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + lradc->buffer[j] = readl(lradc->base + LRADC_CH(j)); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(j)); + lradc->buffer[j] &= LRADC_CH_VALUE_MASK; + lradc->buffer[j] /= LRADC_DELAY_TIMER_LOOP; + j++; + } + + iio_push_to_buffers_with_timestamp(iio, lradc->buffer, pf->timestamp); + + iio_trigger_notify_done(iio->trig); + + return IRQ_HANDLED; +} + +static int mxs_lradc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *iio = iio_trigger_get_drvdata(trig); + struct mxs_lradc *lradc = iio_priv(iio); + const u32 st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR; + + mxs_lradc_reg_wrt(lradc, LRADC_DELAY_KICK, LRADC_DELAY(0) + st); + + return 0; +} + +static const struct iio_trigger_ops mxs_lradc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &mxs_lradc_configure_trigger, +}; + +static int mxs_lradc_trigger_init(struct iio_dev *iio) +{ + int ret; + struct iio_trigger *trig; + struct mxs_lradc *lradc = iio_priv(iio); + + trig = iio_trigger_alloc("%s-dev%i", iio->name, iio->id); + if (!trig) + return -ENOMEM; + + trig->dev.parent = lradc->dev; + iio_trigger_set_drvdata(trig, iio); + trig->ops = &mxs_lradc_trigger_ops; + + ret = iio_trigger_register(trig); + if (ret) { + iio_trigger_free(trig); + return ret; + } + + lradc->trig = trig; + + return 0; +} + +static void mxs_lradc_trigger_remove(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + iio_trigger_unregister(lradc->trig); + iio_trigger_free(lradc->trig); +} + +static int mxs_lradc_buffer_preenable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + int ret = 0, chan, ofs = 0; + unsigned long enable = 0; + u32 ctrl4_set = 0; + u32 ctrl4_clr = 0; + u32 ctrl1_irq = 0; + const u32 chan_value = LRADC_CH_ACCUMULATE | + ((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET); + const int len = bitmap_weight(iio->active_scan_mask, + LRADC_MAX_TOTAL_CHANS); + + if (!len) + return -EINVAL; + + /* + * Lock the driver so raw access can not be done during buffered + * operation. This simplifies the code a lot. + */ + ret = mutex_trylock(&lradc->lock); + if (!ret) + return -EBUSY; + + lradc->buffer = kmalloc_array(len, sizeof(*lradc->buffer), GFP_KERNEL); + if (!lradc->buffer) { + ret = -ENOMEM; + goto err_mem; + } + + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear( + lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + + for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) { + ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs); + ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs); + ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs); + mxs_lradc_reg_wrt(lradc, chan_value, LRADC_CH(ofs)); + bitmap_set(&enable, ofs, 1); + ofs++; + } + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + mxs_lradc_reg_clear(lradc, ctrl4_clr, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl4_set, LRADC_CTRL4); + mxs_lradc_reg_set(lradc, ctrl1_irq, LRADC_CTRL1); + mxs_lradc_reg_set(lradc, enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET, + LRADC_DELAY(0)); + + return 0; + +err_mem: + mutex_unlock(&lradc->lock); + return ret; +} + +static int mxs_lradc_buffer_postdisable(struct iio_dev *iio) +{ + struct mxs_lradc *lradc = iio_priv(iio); + + mxs_lradc_reg_clear(lradc, LRADC_DELAY_TRIGGER_LRADCS_MASK | + LRADC_DELAY_KICK, LRADC_DELAY(0)); + + mxs_lradc_reg_clear(lradc, lradc->buffer_vchans, LRADC_CTRL0); + if (lradc->soc == IMX28_LRADC) + mxs_lradc_reg_clear( + lradc, + lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET, + LRADC_CTRL1); + + kfree(lradc->buffer); + mutex_unlock(&lradc->lock); + + return 0; +} + +static bool mxs_lradc_validate_scan_mask(struct iio_dev *iio, + const unsigned long *mask) +{ + struct mxs_lradc *lradc = iio_priv(iio); + const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS); + int rsvd_chans = 0; + unsigned long rsvd_mask = 0; + + if (lradc->use_touchbutton) + rsvd_mask |= CHAN_MASK_TOUCHBUTTON; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_4WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE; + if (lradc->use_touchscreen == MXS_LRADC_TOUCHSCREEN_5WIRE) + rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE; + + if (lradc->use_touchbutton) + rsvd_chans++; + if (lradc->use_touchscreen) + rsvd_chans += 2; + + /* Test for attempts to map channels with special mode of operation. */ + if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS)) + return false; + + /* Test for attempts to map more channels then available slots. */ + if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS) + return false; + + return true; +} + +static const struct iio_buffer_setup_ops mxs_lradc_buffer_ops = { + .preenable = &mxs_lradc_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &mxs_lradc_buffer_postdisable, + .validate_scan_mask = &mxs_lradc_validate_scan_mask, +}; + +/* + * Driver initialization + */ + +#define MXS_ADC_CHAN(idx, chan_type, name) { \ + .type = (chan_type), \ + .indexed = 1, \ + .scan_index = (idx), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .channel = (idx), \ + .address = (idx), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = LRADC_RESOLUTION, \ + .storagebits = 32, \ + }, \ + .datasheet_name = (name), \ +} + +static const struct iio_chan_spec mx23_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"), + MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"), + MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"), + MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"), + MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"), + MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"), + MXS_ADC_CHAN(6, IIO_VOLTAGE, "VDDIO"), + MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"), + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + .datasheet_name = "TEMP_DIE", + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE, NULL), + MXS_ADC_CHAN(11, IIO_VOLTAGE, NULL), + MXS_ADC_CHAN(12, IIO_VOLTAGE, "USB_DP"), + MXS_ADC_CHAN(13, IIO_VOLTAGE, "USB_DN"), + MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"), + MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"), +}; + +static const struct iio_chan_spec mx28_lradc_chan_spec[] = { + MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"), + MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"), + MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"), + MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"), + MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"), + MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"), + MXS_ADC_CHAN(6, IIO_VOLTAGE, "LRADC6"), + MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"), + /* Combined Temperature sensors */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = 8, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .channel = 8, + .scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,}, + .datasheet_name = "TEMP_DIE", + }, + /* Hidden channel to keep indexes */ + { + .type = IIO_TEMP, + .indexed = 1, + .scan_index = -1, + .channel = 9, + }, + MXS_ADC_CHAN(10, IIO_VOLTAGE, "VDDIO"), + MXS_ADC_CHAN(11, IIO_VOLTAGE, "VTH"), + MXS_ADC_CHAN(12, IIO_VOLTAGE, "VDDA"), + MXS_ADC_CHAN(13, IIO_VOLTAGE, "VDDD"), + MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"), + MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"), +}; + +static void mxs_lradc_hw_init(struct mxs_lradc *lradc) +{ + /* The ADC always uses DELAY CHANNEL 0. */ + const u32 adc_cfg = + (1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) | + (LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET); + + /* Configure DELAY CHANNEL 0 for generic ADC sampling. */ + mxs_lradc_reg_wrt(lradc, adc_cfg, LRADC_DELAY(0)); + + /* Disable remaining DELAY CHANNELs */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(1)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(2)); + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(3)); + + /* Start internal temperature sensing. */ + mxs_lradc_reg_wrt(lradc, 0, LRADC_CTRL2); +} + +static void mxs_lradc_hw_stop(struct mxs_lradc *lradc) +{ + int i; + + mxs_lradc_reg_clear(lradc, mxs_lradc_irq_en_mask(lradc), LRADC_CTRL1); + + for (i = 0; i < LRADC_MAX_DELAY_CHANS; i++) + mxs_lradc_reg_wrt(lradc, 0, LRADC_DELAY(i)); +} + +static const struct of_device_id mxs_lradc_dt_ids[] = { + { .compatible = "fsl,imx23-lradc", .data = (void *)IMX23_LRADC, }, + { .compatible = "fsl,imx28-lradc", .data = (void *)IMX28_LRADC, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxs_lradc_dt_ids); + +static int mxs_lradc_probe_touchscreen(struct mxs_lradc *lradc, + struct device_node *lradc_node) +{ + int ret; + u32 ts_wires = 0, adapt; + + ret = of_property_read_u32(lradc_node, "fsl,lradc-touchscreen-wires", + &ts_wires); + if (ret) + return -ENODEV; /* touchscreen feature disabled */ + + switch (ts_wires) { + case 4: + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_4WIRE; + break; + case 5: + if (lradc->soc == IMX28_LRADC) { + lradc->use_touchscreen = MXS_LRADC_TOUCHSCREEN_5WIRE; + break; + } + /* fall through an error message for i.MX23 */ + default: + dev_err(lradc->dev, + "Unsupported number of touchscreen wires (%d)\n", + ts_wires); + return -EINVAL; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-ctrl", &adapt)) { + lradc->over_sample_cnt = 4; + } else { + if (adapt < 1 || adapt > 32) { + dev_err(lradc->dev, "Invalid sample count (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_cnt = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,ave-delay", &adapt)) { + lradc->over_sample_delay = 2; + } else { + if (adapt < 2 || adapt > LRADC_DELAY_DELAY_MASK + 1) { + dev_err(lradc->dev, "Invalid sample delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->over_sample_delay = adapt; + } + + if (of_property_read_u32(lradc_node, "fsl,settling", &adapt)) { + lradc->settling_delay = 10; + } else { + if (adapt < 1 || adapt > LRADC_DELAY_DELAY_MASK) { + dev_err(lradc->dev, "Invalid settling delay (%u)\n", + adapt); + return -EINVAL; + } + lradc->settling_delay = adapt; + } + + return 0; +} + +static int mxs_lradc_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(mxs_lradc_dt_ids, &pdev->dev); + const struct mxs_lradc_of_config *of_cfg = + &mxs_lradc_of_config[(enum mxs_lradc_id)of_id->data]; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct mxs_lradc *lradc; + struct iio_dev *iio; + struct resource *iores; + int ret = 0, touch_ret; + int i, s; + u64 scale_uv; + + /* Allocate the IIO device. */ + iio = devm_iio_device_alloc(dev, sizeof(*lradc)); + if (!iio) { + dev_err(dev, "Failed to allocate IIO device\n"); + return -ENOMEM; + } + + lradc = iio_priv(iio); + lradc->soc = (enum mxs_lradc_id)of_id->data; + + /* Grab the memory area */ + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lradc->dev = &pdev->dev; + lradc->base = devm_ioremap_resource(dev, iores); + if (IS_ERR(lradc->base)) + return PTR_ERR(lradc->base); + + lradc->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(lradc->clk)) { + dev_err(dev, "Failed to get the delay unit clock\n"); + return PTR_ERR(lradc->clk); + } + ret = clk_prepare_enable(lradc->clk); + if (ret != 0) { + dev_err(dev, "Failed to enable the delay unit clock\n"); + return ret; + } + + touch_ret = mxs_lradc_probe_touchscreen(lradc, node); + + if (touch_ret == 0) + lradc->buffer_vchans = BUFFER_VCHANS_LIMITED; + else + lradc->buffer_vchans = BUFFER_VCHANS_ALL; + + /* Grab all IRQ sources */ + for (i = 0; i < of_cfg->irq_count; i++) { + lradc->irq[i] = platform_get_irq(pdev, i); + if (lradc->irq[i] < 0) { + ret = lradc->irq[i]; + goto err_clk; + } + + ret = devm_request_irq(dev, lradc->irq[i], + mxs_lradc_handle_irq, 0, + of_cfg->irq_name[i], iio); + if (ret) + goto err_clk; + } + + lradc->vref_mv = of_cfg->vref_mv; + + platform_set_drvdata(pdev, iio); + + init_completion(&lradc->completion); + mutex_init(&lradc->lock); + + iio->name = pdev->name; + iio->dev.parent = &pdev->dev; + iio->info = &mxs_lradc_iio_info; + iio->modes = INDIO_DIRECT_MODE; + iio->masklength = LRADC_MAX_TOTAL_CHANS; + + if (lradc->soc == IMX23_LRADC) { + iio->channels = mx23_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mx23_lradc_chan_spec); + } else { + iio->channels = mx28_lradc_chan_spec; + iio->num_channels = ARRAY_SIZE(mx28_lradc_chan_spec); + } + + ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time, + &mxs_lradc_trigger_handler, + &mxs_lradc_buffer_ops); + if (ret) + goto err_clk; + + ret = mxs_lradc_trigger_init(iio); + if (ret) + goto err_trig; + + /* Populate available ADC input ranges */ + for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) { + for (s = 0; s < ARRAY_SIZE(lradc->scale_avail[i]); s++) { + /* + * [s=0] = optional divider by two disabled (default) + * [s=1] = optional divider by two enabled + * + * The scale is calculated by doing: + * Vref >> (realbits - s) + * which multiplies by two on the second component + * of the array. + */ + scale_uv = ((u64)lradc->vref_mv[i] * 100000000) >> + (LRADC_RESOLUTION - s); + lradc->scale_avail[i][s].nano = + do_div(scale_uv, 100000000) * 10; + lradc->scale_avail[i][s].integer = scale_uv; + } + } + + ret = stmp_reset_block(lradc->base); + if (ret) + goto err_dev; + + /* Configure the hardware. */ + mxs_lradc_hw_init(lradc); + + /* Register the touchscreen input device. */ + if (touch_ret == 0) { + ret = mxs_lradc_ts_register(lradc); + if (ret) + goto err_ts_register; + } + + /* Register IIO device. */ + ret = iio_device_register(iio); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + goto err_ts; + } + + return 0; + +err_ts: + mxs_lradc_ts_unregister(lradc); +err_ts_register: + mxs_lradc_hw_stop(lradc); +err_dev: + mxs_lradc_trigger_remove(iio); +err_trig: + iio_triggered_buffer_cleanup(iio); +err_clk: + clk_disable_unprepare(lradc->clk); + return ret; +} + +static int mxs_lradc_remove(struct platform_device *pdev) +{ + struct iio_dev *iio = platform_get_drvdata(pdev); + struct mxs_lradc *lradc = iio_priv(iio); + + iio_device_unregister(iio); + mxs_lradc_ts_unregister(lradc); + mxs_lradc_hw_stop(lradc); + mxs_lradc_trigger_remove(iio); + iio_triggered_buffer_cleanup(iio); + + clk_disable_unprepare(lradc->clk); + + return 0; +} + +static struct platform_driver mxs_lradc_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxs_lradc_dt_ids, + }, + .probe = mxs_lradc_probe, + .remove = mxs_lradc_remove, +}; + +module_platform_driver(mxs_lradc_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Freescale MXS LRADC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git b/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c new file mode 100644 index 0000000..2bbf0c5 --- /dev/null +++ b/drivers/iio/adc/palmas_gpadc.c @@ -0,0 +1,859 @@ +/* + * palmas-adc.c -- TI PALMAS GPADC. + * + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved. + * + * Author: Pradeep Goudagunta + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MOD_NAME "palmas-gpadc" +#define PALMAS_ADC_CONVERSION_TIMEOUT (msecs_to_jiffies(5000)) +#define PALMAS_TO_BE_CALCULATED 0 +#define PALMAS_GPADC_TRIMINVALID -1 + +struct palmas_gpadc_info { +/* calibration codes and regs */ + int x1; /* lower ideal code */ + int x2; /* higher ideal code */ + int v1; /* expected lower volt reading */ + int v2; /* expected higher volt reading */ + u8 trim1_reg; /* register number for lower trim */ + u8 trim2_reg; /* register number for upper trim */ + int gain; /* calculated from above (after reading trim regs) */ + int offset; /* calculated from above (after reading trim regs) */ + int gain_error; /* calculated from above (after reading trim regs) */ + bool is_uncalibrated; /* if channel has calibration data */ +}; + +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \ + [PALMAS_ADC_CH_##_chan] = { \ + .x1 = _x1, \ + .x2 = _x2, \ + .v1 = _v1, \ + .v2 = _v2, \ + .gain = PALMAS_TO_BE_CALCULATED, \ + .offset = PALMAS_TO_BE_CALCULATED, \ + .gain_error = PALMAS_TO_BE_CALCULATED, \ + .trim1_reg = PALMAS_GPADC_TRIM##_t1, \ + .trim2_reg = PALMAS_GPADC_TRIM##_t2, \ + .is_uncalibrated = _is_uncalibrated \ + } + +static struct palmas_gpadc_info palmas_gpadc_info[] = { + PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false), + PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false), + PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false), + PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false), + PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false), + PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false), + PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false), + PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true), + PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false), + PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true), +}; + +/** + * struct palmas_gpadc - the palmas_gpadc structure + * @ch0_current: channel 0 current source setting + * 0: 0 uA + * 1: 5 uA + * 2: 15 uA + * 3: 20 uA + * @ch3_current: channel 0 current source setting + * 0: 0 uA + * 1: 10 uA + * 2: 400 uA + * 3: 800 uA + * @extended_delay: enable the gpadc extended delay mode + * @auto_conversion_period: define the auto_conversion_period + * + * This is the palmas_gpadc structure to store run-time information + * and pointers for this driver instance. + */ + +struct palmas_gpadc { + struct device *dev; + struct palmas *palmas; + u8 ch0_current; + u8 ch3_current; + bool extended_delay; + int irq; + int irq_auto_0; + int irq_auto_1; + struct palmas_gpadc_info *adc_info; + struct completion conv_completion; + struct palmas_adc_wakeup_property wakeup1_data; + struct palmas_adc_wakeup_property wakeup2_data; + bool wakeup1_enable; + bool wakeup2_enable; + int auto_conversion_period; +}; + +/* + * GPADC lock issue in AUTO mode. + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO + * mode feature. + * Details: + * When the AUTO mode is the only conversion mode enabled, if the AUTO + * mode feature is disabled with bit GPADC_AUTO_CTRL. AUTO_CONV1_EN = 0 + * or bit GPADC_AUTO_CTRL. AUTO_CONV0_EN = 0 during a conversion, the + * conversion mechanism can be seen as locked meaning that all following + * conversion will give 0 as a result. Bit GPADC_STATUS.GPADC_AVAILABLE + * will stay at 0 meaning that GPADC is busy. An RT conversion can unlock + * the GPADC. + * + * Workaround(s): + * To avoid the lock mechanism, the workaround to follow before any stop + * conversion request is: + * Force the GPADC state machine to be ON by using the GPADC_CTRL1. + * GPADC_FORCE bit = 1 + * Shutdown the GPADC AUTO conversion using + * GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0. + * After 100us, force the GPADC state machine to be OFF by using the + * GPADC_CTRL1. GPADC_FORCE bit = 0 + */ + +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, + PALMAS_GPADC_CTRL1_GPADC_FORCE); + if (ret < 0) { + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 | + PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0, + 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret); + return ret; + } + + udelay(100); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) + dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret); + + return ret; +} + +static irqreturn_t palmas_gpadc_irq(int irq, void *data) +{ + struct palmas_gpadc *adc = data; + + complete(&adc->conv_completion); + + return IRQ_HANDLED; +} + +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data) +{ + struct palmas_gpadc *adc = data; + + dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq); + palmas_disable_auto_conversion(adc); + + return IRQ_HANDLED; +} + +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, + bool mask) +{ + int ret; + + if (!mask) + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, 0); + else + ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE, + PALMAS_INT3_MASK, + PALMAS_INT3_MASK_GPADC_EOC_SW, + PALMAS_INT3_MASK_GPADC_EOC_SW); + if (ret < 0) + dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan, + int enable) +{ + unsigned int mask, val; + int ret; + + if (enable) { + val = (adc->extended_delay + << PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_RT_CTRL, + PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val); + if (ret < 0) { + dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK | + PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK | + PALMAS_GPADC_CTRL1_GPADC_FORCE); + val = (adc->ch0_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT); + val |= (adc->ch3_current + << PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT); + val |= PALMAS_GPADC_CTRL1_GPADC_FORCE; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, mask, val); + if (ret < 0) { + dev_err(adc->dev, + "Failed to update current setting: %d\n", ret); + return ret; + } + + mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK | + PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, mask, val); + if (ret < 0) { + dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret); + return ret; + } + } else { + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, 0); + if (ret < 0) + dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret); + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_CTRL1, + PALMAS_GPADC_CTRL1_GPADC_FORCE, 0); + if (ret < 0) { + dev_err(adc->dev, "CTRL1 update failed: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan) +{ + int ret; + + ret = palmas_gpadc_enable(adc, adc_chan, true); + if (ret < 0) + return ret; + + return palmas_gpadc_start_mask_interrupt(adc, 0); +} + +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan) +{ + palmas_gpadc_start_mask_interrupt(adc, 1); + palmas_gpadc_enable(adc, adc_chan, false); +} + +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan) +{ + int k; + int d1; + int d2; + int ret; + int gain; + int x1 = adc->adc_info[adc_chan].x1; + int x2 = adc->adc_info[adc_chan].x2; + int v1 = adc->adc_info[adc_chan].v1; + int v2 = adc->adc_info[adc_chan].v2; + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim1_reg, &d1); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE, + adc->adc_info[adc_chan].trim2_reg, &d2); + if (ret < 0) { + dev_err(adc->dev, "TRIM read failed: %d\n", ret); + goto scrub; + } + + /* gain error calculation */ + k = (1000 + (1000 * (d2 - d1)) / (x2 - x1)); + + /* gain calculation */ + gain = ((v2 - v1) * 1000) / (x2 - x1); + + adc->adc_info[adc_chan].gain_error = k; + adc->adc_info[adc_chan].gain = gain; + /* offset Calculation */ + adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1); + +scrub: + return ret; +} + +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan) +{ + unsigned int val; + int ret; + + init_completion(&adc->conv_completion); + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_SELECT, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0, + PALMAS_GPADC_SW_SELECT_SW_START_CONV0); + if (ret < 0) { + dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret); + return ret; + } + + ret = wait_for_completion_timeout(&adc->conv_completion, + PALMAS_ADC_CONVERSION_TIMEOUT); + if (ret == 0) { + dev_err(adc->dev, "conversion not completed\n"); + return -ETIMEDOUT; + } + + ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_SW_CONV0_LSB, &val, 2); + if (ret < 0) { + dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret); + return ret; + } + + ret = val & 0xFFF; + + return ret; +} + +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc, + int adc_chan, int val) +{ + if (!adc->adc_info[adc_chan].is_uncalibrated) + val = (val*1000 - adc->adc_info[adc_chan].offset) / + adc->adc_info[adc_chan].gain_error; + + if (val < 0) { + dev_err(adc->dev, "Mismatch with calibration\n"); + return 0; + } + + val = (val * adc->adc_info[adc_chan].gain) / 1000; + + return val; +} + +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct palmas_gpadc *adc = iio_priv(indio_dev); + int adc_chan = chan->channel; + int ret = 0; + + if (adc_chan > PALMAS_ADC_CH_MAX) + return -EINVAL; + + mutex_lock(&indio_dev->mlock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = palmas_gpadc_read_prepare(adc, adc_chan); + if (ret < 0) + goto out; + + ret = palmas_gpadc_start_conversion(adc, adc_chan); + if (ret < 0) { + dev_err(adc->dev, + "ADC start conversion failed\n"); + goto out; + } + + if (mask == IIO_CHAN_INFO_PROCESSED) + ret = palmas_gpadc_get_calibrated_code( + adc, adc_chan, ret); + + *val = ret; + + ret = IIO_VAL_INT; + goto out; + } + + mutex_unlock(&indio_dev->mlock); + return ret; + +out: + palmas_gpadc_read_done(adc, adc_chan); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static const struct iio_info palmas_gpadc_iio_info = { + .read_raw = palmas_gpadc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info) \ +{ \ + .datasheet_name = PALMAS_DATASHEET_NAME(chan), \ + .type = _type, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(chan_info), \ + .indexed = 1, \ + .channel = PALMAS_ADC_CH_##chan, \ +} + +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = { + PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW), + PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), + PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED), +}; + +static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev, + struct palmas_gpadc_platform_data **gpadc_pdata) +{ + struct device_node *np = pdev->dev.of_node; + struct palmas_gpadc_platform_data *gp_data; + int ret; + u32 pval; + + gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL); + if (!gp_data) + return -ENOMEM; + + ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval); + if (!ret) + gp_data->ch0_current = pval; + + ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval); + if (!ret) + gp_data->ch3_current = pval; + + gp_data->extended_delay = of_property_read_bool(np, + "ti,enable-extended-delay"); + + *gpadc_pdata = gp_data; + + return 0; +} + +static int palmas_gpadc_probe(struct platform_device *pdev) +{ + struct palmas_gpadc *adc; + struct palmas_platform_data *pdata; + struct palmas_gpadc_platform_data *gpadc_pdata = NULL; + struct iio_dev *indio_dev; + int ret, i; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (pdata && pdata->gpadc_pdata) + gpadc_pdata = pdata->gpadc_pdata; + + if (!gpadc_pdata && pdev->dev.of_node) { + ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata); + if (ret < 0) + return ret; + } + if (!gpadc_pdata) + return -EINVAL; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc)); + if (!indio_dev) { + dev_err(&pdev->dev, "iio_device_alloc failed\n"); + return -ENOMEM; + } + + adc = iio_priv(indio_dev); + adc->dev = &pdev->dev; + adc->palmas = dev_get_drvdata(pdev->dev.parent); + adc->adc_info = palmas_gpadc_info; + init_completion(&adc->conv_completion); + dev_set_drvdata(&pdev->dev, indio_dev); + + adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms; + adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ); + if (adc->irq < 0) { + dev_err(adc->dev, + "get virq failed: %d\n", adc->irq); + ret = adc->irq; + goto out; + } + ret = request_threaded_irq(adc->irq, NULL, + palmas_gpadc_irq, + IRQF_ONESHOT, dev_name(adc->dev), + adc); + if (ret < 0) { + dev_err(adc->dev, + "request irq %d failed: %d\n", adc->irq, ret); + goto out; + } + + if (gpadc_pdata->adc_wakeup1_data) { + memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data, + sizeof(adc->wakeup1_data)); + adc->wakeup1_enable = true; + adc->irq_auto_0 = platform_get_irq(pdev, 1); + ret = request_threaded_irq(adc->irq_auto_0, NULL, + palmas_gpadc_irq_auto, + IRQF_ONESHOT, + "palmas-adc-auto-0", adc); + if (ret < 0) { + dev_err(adc->dev, "request auto0 irq %d failed: %d\n", + adc->irq_auto_0, ret); + goto out_irq_free; + } + } + + if (gpadc_pdata->adc_wakeup2_data) { + memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data, + sizeof(adc->wakeup2_data)); + adc->wakeup2_enable = true; + adc->irq_auto_1 = platform_get_irq(pdev, 2); + ret = request_threaded_irq(adc->irq_auto_1, NULL, + palmas_gpadc_irq_auto, + IRQF_ONESHOT, + "palmas-adc-auto-1", adc); + if (ret < 0) { + dev_err(adc->dev, "request auto1 irq %d failed: %d\n", + adc->irq_auto_1, ret); + goto out_irq_auto0_free; + } + } + + /* set the current source 0 (value 0/5/15/20 uA => 0..3) */ + if (gpadc_pdata->ch0_current <= 1) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0; + else if (gpadc_pdata->ch0_current <= 5) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5; + else if (gpadc_pdata->ch0_current <= 15) + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15; + else + adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20; + + /* set the current source 3 (value 0/10/400/800 uA => 0..3) */ + if (gpadc_pdata->ch3_current <= 1) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0; + else if (gpadc_pdata->ch3_current <= 10) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10; + else if (gpadc_pdata->ch3_current <= 400) + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400; + else + adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800; + + adc->extended_delay = gpadc_pdata->extended_delay; + + indio_dev->name = MOD_NAME; + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &palmas_gpadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = palmas_gpadc_iio_channel; + indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(adc->dev, "iio_device_register() failed: %d\n", ret); + goto out_irq_auto1_free; + } + + device_set_wakeup_capable(&pdev->dev, 1); + for (i = 0; i < PALMAS_ADC_CH_MAX; i++) { + if (!(adc->adc_info[i].is_uncalibrated)) + palmas_gpadc_calibrate(adc, i); + } + + if (adc->wakeup1_enable || adc->wakeup2_enable) + device_wakeup_enable(&pdev->dev); + + return 0; + +out_irq_auto1_free: + if (gpadc_pdata->adc_wakeup2_data) + free_irq(adc->irq_auto_1, adc); +out_irq_auto0_free: + if (gpadc_pdata->adc_wakeup1_data) + free_irq(adc->irq_auto_0, adc); +out_irq_free: + free_irq(adc->irq, adc); +out: + return ret; +} + +static int palmas_gpadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + + if (adc->wakeup1_enable || adc->wakeup2_enable) + device_wakeup_disable(&pdev->dev); + iio_device_unregister(indio_dev); + free_irq(adc->irq, adc); + if (adc->wakeup1_enable) + free_irq(adc->irq_auto_0, adc); + if (adc->wakeup2_enable) + free_irq(adc->irq_auto_1, adc); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc) +{ + int adc_period, conv; + int i; + int ch0 = 0, ch1 = 0; + int thres; + int ret; + + adc_period = adc->auto_conversion_period; + for (i = 0; i < 16; ++i) { + if (((1000 * (1 << i)) / 32) < adc_period) + continue; + } + if (i > 0) + i--; + adc_period = i; + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK, + adc_period); + if (ret < 0) { + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + return ret; + } + + conv = 0; + if (adc->wakeup1_enable) { + int polarity; + + ch0 = adc->wakeup1_data.adc_channel_number; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN; + if (adc->wakeup1_data.adc_high_threshold > 0) { + thres = adc->wakeup1_data.adc_high_threshold; + polarity = 0; + } else { + thres = adc->wakeup1_data.adc_low_threshold; + polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV0_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV0_MSB write failed: %d\n", ret); + return ret; + } + } + + if (adc->wakeup2_enable) { + int polarity; + + ch1 = adc->wakeup2_data.adc_channel_number; + conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN; + if (adc->wakeup2_data.adc_high_threshold > 0) { + thres = adc->wakeup2_data.adc_high_threshold; + polarity = 0; + } else { + thres = adc->wakeup2_data.adc_low_threshold; + polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_LSB write failed: %d\n", ret); + return ret; + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_THRES_CONV1_MSB, + ((thres >> 8) & 0xF) | polarity); + if (ret < 0) { + dev_err(adc->dev, + "THRES_CONV1_MSB write failed: %d\n", ret); + return ret; + } + } + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_CTRL, + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN | + PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv); + if (ret < 0) + dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret); + + return ret; +} + +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc) +{ + int ret; + + ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE, + PALMAS_GPADC_AUTO_SELECT, 0); + if (ret < 0) { + dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret); + return ret; + } + + ret = palmas_disable_auto_conversion(adc); + if (ret < 0) + dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret); + + return ret; +} + +static int palmas_gpadc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + int wakeup = adc->wakeup1_enable || adc->wakeup2_enable; + int ret; + + if (!device_may_wakeup(dev) || !wakeup) + return 0; + + ret = palmas_adc_wakeup_configure(adc); + if (ret < 0) + return ret; + + if (adc->wakeup1_enable) + enable_irq_wake(adc->irq_auto_0); + + if (adc->wakeup2_enable) + enable_irq_wake(adc->irq_auto_1); + + return 0; +} + +static int palmas_gpadc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct palmas_gpadc *adc = iio_priv(indio_dev); + int wakeup = adc->wakeup1_enable || adc->wakeup2_enable; + int ret; + + if (!device_may_wakeup(dev) || !wakeup) + return 0; + + ret = palmas_adc_wakeup_reset(adc); + if (ret < 0) + return ret; + + if (adc->wakeup1_enable) + disable_irq_wake(adc->irq_auto_0); + + if (adc->wakeup2_enable) + disable_irq_wake(adc->irq_auto_1); + + return 0; +}; +#endif + +static const struct dev_pm_ops palmas_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend, + palmas_gpadc_resume) +}; + +static const struct of_device_id of_palmas_gpadc_match_tbl[] = { + { .compatible = "ti,palmas-gpadc", }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl); + +static struct platform_driver palmas_gpadc_driver = { + .probe = palmas_gpadc_probe, + .remove = palmas_gpadc_remove, + .driver = { + .name = MOD_NAME, + .pm = &palmas_pm_ops, + .of_match_table = of_palmas_gpadc_match_tbl, + }, +}; + +static int __init palmas_gpadc_init(void) +{ + return platform_driver_register(&palmas_gpadc_driver); +} +module_init(palmas_gpadc_init); + +static void __exit palmas_gpadc_exit(void) +{ + platform_driver_unregister(&palmas_gpadc_driver); +} +module_exit(palmas_gpadc_exit); + +MODULE_DESCRIPTION("palmas GPADC driver"); +MODULE_AUTHOR("Pradeep Goudagunta"); +MODULE_ALIAS("platform:palmas-gpadc"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/rockchip_saradc.c b/drivers/iio/adc/rockchip_saradc.c index 9c311c1..f9ad6c2 100644 --- a/drivers/iio/adc/rockchip_saradc.c +++ b/drivers/iio/adc/rockchip_saradc.c @@ -159,6 +159,22 @@ static const struct rockchip_saradc_data rk3066_tsadc_data = { .clk_rate = 50000, }; +static const struct iio_chan_spec rockchip_rk3399_saradc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), + ADC_CHANNEL(2, "adc2"), + ADC_CHANNEL(3, "adc3"), + ADC_CHANNEL(4, "adc4"), + ADC_CHANNEL(5, "adc5"), +}; + +static const struct rockchip_saradc_data rk3399_saradc_data = { + .num_bits = 10, + .channels = rockchip_rk3399_saradc_iio_channels, + .num_channels = ARRAY_SIZE(rockchip_rk3399_saradc_iio_channels), + .clk_rate = 1000000, +}; + static const struct of_device_id rockchip_saradc_match[] = { { .compatible = "rockchip,saradc", @@ -166,6 +182,9 @@ static const struct of_device_id rockchip_saradc_match[] = { }, { .compatible = "rockchip,rk3066-tsadc", .data = &rk3066_tsadc_data, + }, { + .compatible = "rockchip,rk3399-saradc", + .data = &rk3399_saradc_data, }, {}, }; diff --git a/drivers/iio/adc/ti-adc081c.c b/drivers/iio/adc/ti-adc081c.c index 2c8374f..9fd032d 100644 --- a/drivers/iio/adc/ti-adc081c.c +++ b/drivers/iio/adc/ti-adc081c.c @@ -1,9 +1,21 @@ /* + * TI ADC081C/ADC101C/ADC121C 8/10/12-bit ADC driver + * * Copyright (C) 2012 Avionic Design GmbH + * Copyright (C) 2016 Intel * * 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. + * + * Datasheets: + * http://www.ti.com/lit/ds/symlink/adc081c021.pdf + * http://www.ti.com/lit/ds/symlink/adc101c021.pdf + * http://www.ti.com/lit/ds/symlink/adc121c021.pdf + * + * The devices have a very similar interface and differ mostly in the number of + * bits handled. For the 8-bit and 10-bit models the least-significant 4 or 2 + * bits of value registers are reserved. */ #include @@ -12,11 +24,17 @@ #include #include +#include +#include +#include #include struct adc081c { struct i2c_client *i2c; struct regulator *ref; + + /* 8, 10 or 12 */ + int bits; }; #define REG_CONV_RES 0x00 @@ -34,7 +52,7 @@ static int adc081c_read_raw(struct iio_dev *iio, if (err < 0) return err; - *value = (err >> 4) & 0xff; + *value = (err & 0xFFF) >> (12 - adc->bits); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: @@ -43,7 +61,7 @@ static int adc081c_read_raw(struct iio_dev *iio, return err; *value = err / 1000; - *shift = 8; + *shift = adc->bits; return IIO_VAL_FRACTIONAL_LOG2; @@ -54,10 +72,53 @@ static int adc081c_read_raw(struct iio_dev *iio, return -EINVAL; } -static const struct iio_chan_spec adc081c_channel = { - .type = IIO_VOLTAGE, - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), +#define ADCxx1C_CHAN(_bits) { \ + .type = IIO_VOLTAGE, \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 12 - (_bits), \ + .endianness = IIO_CPU, \ + }, \ +} + +#define DEFINE_ADCxx1C_CHANNELS(_name, _bits) \ + static const struct iio_chan_spec _name ## _channels[] = { \ + ADCxx1C_CHAN((_bits)), \ + IIO_CHAN_SOFT_TIMESTAMP(1), \ + }; \ + +#define ADC081C_NUM_CHANNELS 2 + +struct adcxx1c_model { + const struct iio_chan_spec* channels; + int bits; +}; + +#define ADCxx1C_MODEL(_name, _bits) \ + { \ + .channels = _name ## _channels, \ + .bits = (_bits), \ + } + +DEFINE_ADCxx1C_CHANNELS(adc081c, 8); +DEFINE_ADCxx1C_CHANNELS(adc101c, 10); +DEFINE_ADCxx1C_CHANNELS(adc121c, 12); + +/* Model ids are indexes in _models array */ +enum adcxx1c_model_id { + ADC081C = 0, + ADC101C = 1, + ADC121C = 2, +}; + +static struct adcxx1c_model adcxx1c_models[] = { + ADCxx1C_MODEL(adc081c, 8), + ADCxx1C_MODEL(adc101c, 10), + ADCxx1C_MODEL(adc121c, 12), }; static const struct iio_info adc081c_info = { @@ -65,15 +126,34 @@ static const struct iio_info adc081c_info = { .driver_module = THIS_MODULE, }; +static irqreturn_t adc081c_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct adc081c *data = iio_priv(indio_dev); + u16 buf[8]; /* 2 bytes data + 6 bytes padding + 8 bytes timestamp */ + int ret; + + ret = i2c_smbus_read_word_swapped(data->i2c, REG_CONV_RES); + if (ret < 0) + goto out; + buf[0] = ret; + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); +out: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + static int adc081c_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct iio_dev *iio; struct adc081c *adc; + struct adcxx1c_model *model = &adcxx1c_models[id->driver_data]; int err; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + return -EOPNOTSUPP; iio = devm_iio_device_alloc(&client->dev, sizeof(*adc)); if (!iio) @@ -81,6 +161,7 @@ static int adc081c_probe(struct i2c_client *client, adc = iio_priv(iio); adc->i2c = client; + adc->bits = model->bits; adc->ref = devm_regulator_get(&client->dev, "vref"); if (IS_ERR(adc->ref)) @@ -95,18 +176,26 @@ static int adc081c_probe(struct i2c_client *client, iio->modes = INDIO_DIRECT_MODE; iio->info = &adc081c_info; - iio->channels = &adc081c_channel; - iio->num_channels = 1; + iio->channels = model->channels; + iio->num_channels = ADC081C_NUM_CHANNELS; + + err = iio_triggered_buffer_setup(iio, NULL, adc081c_trigger_handler, NULL); + if (err < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + goto err_regulator_disable; + } err = iio_device_register(iio); if (err < 0) - goto regulator_disable; + goto err_buffer_cleanup; i2c_set_clientdata(client, iio); return 0; -regulator_disable: +err_buffer_cleanup: + iio_triggered_buffer_cleanup(iio); +err_regulator_disable: regulator_disable(adc->ref); return err; @@ -118,13 +207,16 @@ static int adc081c_remove(struct i2c_client *client) struct adc081c *adc = iio_priv(iio); iio_device_unregister(iio); + iio_triggered_buffer_cleanup(iio); regulator_disable(adc->ref); return 0; } static const struct i2c_device_id adc081c_id[] = { - { "adc081c", 0 }, + { "adc081c", ADC081C }, + { "adc101c", ADC101C }, + { "adc121c", ADC121C }, { } }; MODULE_DEVICE_TABLE(i2c, adc081c_id); @@ -132,6 +224,8 @@ MODULE_DEVICE_TABLE(i2c, adc081c_id); #ifdef CONFIG_OF static const struct of_device_id adc081c_of_match[] = { { .compatible = "ti,adc081c" }, + { .compatible = "ti,adc101c" }, + { .compatible = "ti,adc121c" }, { } }; MODULE_DEVICE_TABLE(of, adc081c_of_match); @@ -149,5 +243,5 @@ static struct i2c_driver adc081c_driver = { module_i2c_driver(adc081c_driver); MODULE_AUTHOR("Thierry Reding "); -MODULE_DESCRIPTION("Texas Instruments ADC081C021/027 driver"); +MODULE_DESCRIPTION("Texas Instruments ADC081C/ADC101C/ADC121C driver"); MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/ti-adc0832.c b/drivers/iio/adc/ti-adc0832.c new file mode 100644 index 0000000..0afeac0 --- /dev/null +++ b/drivers/iio/adc/ti-adc0832.c @@ -0,0 +1,288 @@ +/* + * ADC0831/ADC0832/ADC0834/ADC0838 8-bit ADC driver + * + * Copyright (c) 2016 Akinobu Mita + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Datasheet: http://www.ti.com/lit/ds/symlink/adc0832-n.pdf + */ + +#include +#include +#include +#include + +enum { + adc0831, + adc0832, + adc0834, + adc0838, +}; + +struct adc0832 { + struct spi_device *spi; + struct regulator *reg; + struct mutex lock; + u8 mux_bits; + + u8 tx_buf[2] ____cacheline_aligned; + u8 rx_buf[2]; +}; + +#define ADC0832_VOLTAGE_CHANNEL(chan) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +#define ADC0832_VOLTAGE_CHANNEL_DIFF(chan1, chan2) \ + { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (chan1), \ + .channel2 = (chan2), \ + .differential = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \ + } + +static const struct iio_chan_spec adc0831_channels[] = { + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1), +}; + +static const struct iio_chan_spec adc0832_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0), +}; + +static const struct iio_chan_spec adc0834_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL(2), + ADC0832_VOLTAGE_CHANNEL(3), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0), + ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3), + ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2), +}; + +static const struct iio_chan_spec adc0838_channels[] = { + ADC0832_VOLTAGE_CHANNEL(0), + ADC0832_VOLTAGE_CHANNEL(1), + ADC0832_VOLTAGE_CHANNEL(2), + ADC0832_VOLTAGE_CHANNEL(3), + ADC0832_VOLTAGE_CHANNEL(4), + ADC0832_VOLTAGE_CHANNEL(5), + ADC0832_VOLTAGE_CHANNEL(6), + ADC0832_VOLTAGE_CHANNEL(7), + ADC0832_VOLTAGE_CHANNEL_DIFF(0, 1), + ADC0832_VOLTAGE_CHANNEL_DIFF(1, 0), + ADC0832_VOLTAGE_CHANNEL_DIFF(2, 3), + ADC0832_VOLTAGE_CHANNEL_DIFF(3, 2), + ADC0832_VOLTAGE_CHANNEL_DIFF(4, 5), + ADC0832_VOLTAGE_CHANNEL_DIFF(5, 4), + ADC0832_VOLTAGE_CHANNEL_DIFF(6, 7), + ADC0832_VOLTAGE_CHANNEL_DIFF(7, 6), +}; + +static int adc0831_adc_conversion(struct adc0832 *adc) +{ + struct spi_device *spi = adc->spi; + int ret; + + ret = spi_read(spi, &adc->rx_buf, 2); + if (ret) + return ret; + + /* + * Skip TRI-STATE and a leading zero + */ + return (adc->rx_buf[0] << 2 & 0xff) | (adc->rx_buf[1] >> 6); +} + +static int adc0832_adc_conversion(struct adc0832 *adc, int channel, + bool differential) +{ + struct spi_device *spi = adc->spi; + struct spi_transfer xfer = { + .tx_buf = adc->tx_buf, + .rx_buf = adc->rx_buf, + .len = 2, + }; + int ret; + + if (!adc->mux_bits) + return adc0831_adc_conversion(adc); + + /* start bit */ + adc->tx_buf[0] = 1 << (adc->mux_bits + 1); + /* single-ended or differential */ + adc->tx_buf[0] |= differential ? 0 : (1 << adc->mux_bits); + /* odd / sign */ + adc->tx_buf[0] |= (channel % 2) << (adc->mux_bits - 1); + /* select */ + if (adc->mux_bits > 1) + adc->tx_buf[0] |= channel / 2; + + /* align Data output BIT7 (MSB) to 8-bit boundary */ + adc->tx_buf[0] <<= 1; + + ret = spi_sync_transfer(spi, &xfer, 1); + if (ret) + return ret; + + return adc->rx_buf[1]; +} + +static int adc0832_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *channel, int *value, + int *shift, long mask) +{ + struct adc0832 *adc = iio_priv(iio); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&adc->lock); + *value = adc0832_adc_conversion(adc, channel->channel, + channel->differential); + mutex_unlock(&adc->lock); + if (*value < 0) + return *value; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *value = regulator_get_voltage(adc->reg); + if (*value < 0) + return *value; + + /* convert regulator output voltage to mV */ + *value /= 1000; + *shift = 8; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static const struct iio_info adc0832_info = { + .read_raw = adc0832_read_raw, + .driver_module = THIS_MODULE, +}; + +static int adc0832_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct adc0832 *adc; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->spi = spi; + mutex_init(&adc->lock); + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->info = &adc0832_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + switch (spi_get_device_id(spi)->driver_data) { + case adc0831: + adc->mux_bits = 0; + indio_dev->channels = adc0831_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0831_channels); + break; + case adc0832: + adc->mux_bits = 1; + indio_dev->channels = adc0832_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0832_channels); + break; + case adc0834: + adc->mux_bits = 2; + indio_dev->channels = adc0834_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0834_channels); + break; + case adc0838: + adc->mux_bits = 3; + indio_dev->channels = adc0838_channels; + indio_dev->num_channels = ARRAY_SIZE(adc0838_channels); + break; + default: + return -EINVAL; + } + + adc->reg = devm_regulator_get(&spi->dev, "vref"); + if (IS_ERR(adc->reg)) + return PTR_ERR(adc->reg); + + ret = regulator_enable(adc->reg); + if (ret) + return ret; + + spi_set_drvdata(spi, indio_dev); + + ret = iio_device_register(indio_dev); + if (ret) + regulator_disable(adc->reg); + + return ret; +} + +static int adc0832_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct adc0832 *adc = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regulator_disable(adc->reg); + + return 0; +} + +#ifdef CONFIG_OF + +static const struct of_device_id adc0832_dt_ids[] = { + { .compatible = "ti,adc0831", }, + { .compatible = "ti,adc0832", }, + { .compatible = "ti,adc0834", }, + { .compatible = "ti,adc0838", }, + {} +}; +MODULE_DEVICE_TABLE(of, adc0832_dt_ids); + +#endif + +static const struct spi_device_id adc0832_id[] = { + { "adc0831", adc0831 }, + { "adc0832", adc0832 }, + { "adc0834", adc0834 }, + { "adc0838", adc0838 }, + {} +}; +MODULE_DEVICE_TABLE(spi, adc0832_id); + +static struct spi_driver adc0832_driver = { + .driver = { + .name = "adc0832", + .of_match_table = of_match_ptr(adc0832_dt_ids), + }, + .probe = adc0832_probe, + .remove = adc0832_remove, + .id_table = adc0832_id, +}; +module_spi_driver(adc0832_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("ADC0831/ADC0832/ADC0834/ADC0838 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/ti-adc128s052.c b/drivers/iio/adc/ti-adc128s052.c index ff6f7f6..bc58867 100644 --- a/drivers/iio/adc/ti-adc128s052.c +++ b/drivers/iio/adc/ti-adc128s052.c @@ -1,10 +1,11 @@ /* * Copyright (C) 2014 Angelo Compagnucci * - * Driver for Texas Instruments' ADC128S052 and ADC122S021 ADC chip. + * Driver for Texas Instruments' ADC128S052, ADC122S021 and ADC124S021 ADC chip. * Datasheets can be found here: * http://www.ti.com/lit/ds/symlink/adc128s052.pdf * http://www.ti.com/lit/ds/symlink/adc122s021.pdf + * http://www.ti.com/lit/ds/symlink/adc124s021.pdf * * 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 @@ -114,9 +115,17 @@ static const struct iio_chan_spec adc122s021_channels[] = { ADC128_VOLTAGE_CHANNEL(1), }; +static const struct iio_chan_spec adc124s021_channels[] = { + ADC128_VOLTAGE_CHANNEL(0), + ADC128_VOLTAGE_CHANNEL(1), + ADC128_VOLTAGE_CHANNEL(2), + ADC128_VOLTAGE_CHANNEL(3), +}; + static const struct adc128_configuration adc128_config[] = { { adc128s052_channels, ARRAY_SIZE(adc128s052_channels) }, { adc122s021_channels, ARRAY_SIZE(adc122s021_channels) }, + { adc124s021_channels, ARRAY_SIZE(adc124s021_channels) }, }; static const struct iio_info adc128_info = { @@ -177,6 +186,7 @@ static int adc128_remove(struct spi_device *spi) static const struct of_device_id adc128_of_match[] = { { .compatible = "ti,adc128s052", }, { .compatible = "ti,adc122s021", }, + { .compatible = "ti,adc124s021", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, adc128_of_match); @@ -184,6 +194,7 @@ MODULE_DEVICE_TABLE(of, adc128_of_match); static const struct spi_device_id adc128_id[] = { { "adc128s052", 0}, /* index into adc128_config */ { "adc122s021", 1}, + { "adc124s021", 2}, { } }; MODULE_DEVICE_TABLE(spi, adc128_id); diff --git b/drivers/iio/adc/ti-ads1015.c b/drivers/iio/adc/ti-ads1015.c new file mode 100644 index 0000000..73cbf0b --- /dev/null +++ b/drivers/iio/adc/ti-ads1015.c @@ -0,0 +1,612 @@ +/* + * ADS1015 - Texas Instruments Analog-to-Digital Converter + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for ADS1015 ADC 7-bit I2C slave address: + * * 0x48 - ADDR connected to Ground + * * 0x49 - ADDR connected to Vdd + * * 0x4A - ADDR connected to SDA + * * 0x4B - ADDR connected to SCL + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define ADS1015_DRV_NAME "ads1015" + +#define ADS1015_CONV_REG 0x00 +#define ADS1015_CFG_REG 0x01 + +#define ADS1015_CFG_DR_SHIFT 5 +#define ADS1015_CFG_MOD_SHIFT 8 +#define ADS1015_CFG_PGA_SHIFT 9 +#define ADS1015_CFG_MUX_SHIFT 12 + +#define ADS1015_CFG_DR_MASK GENMASK(7, 5) +#define ADS1015_CFG_MOD_MASK BIT(8) +#define ADS1015_CFG_PGA_MASK GENMASK(11, 9) +#define ADS1015_CFG_MUX_MASK GENMASK(14, 12) + +/* device operating modes */ +#define ADS1015_CONTINUOUS 0 +#define ADS1015_SINGLESHOT 1 + +#define ADS1015_SLEEP_DELAY_MS 2000 +#define ADS1015_DEFAULT_PGA 2 +#define ADS1015_DEFAULT_DATA_RATE 4 +#define ADS1015_DEFAULT_CHAN 0 + +enum ads1015_channels { + ADS1015_AIN0_AIN1 = 0, + ADS1015_AIN0_AIN3, + ADS1015_AIN1_AIN3, + ADS1015_AIN2_AIN3, + ADS1015_AIN0, + ADS1015_AIN1, + ADS1015_AIN2, + ADS1015_AIN3, + ADS1015_TIMESTAMP, +}; + +static const unsigned int ads1015_data_rate[] = { + 128, 250, 490, 920, 1600, 2400, 3300, 3300 +}; + +static const struct { + int scale; + int uscale; +} ads1015_scale[] = { + {3, 0}, + {2, 0}, + {1, 0}, + {0, 500000}, + {0, 250000}, + {0, 125000}, + {0, 125000}, + {0, 125000}, +}; + +#define ADS1015_V_CHAN(_chan, _addr) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _addr, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + }, \ +} + +#define ADS1015_V_DIFF_CHAN(_chan, _chan2, _addr) { \ + .type = IIO_VOLTAGE, \ + .differential = 1, \ + .indexed = 1, \ + .address = _addr, \ + .channel = _chan, \ + .channel2 = _chan2, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _addr, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 12, \ + .storagebits = 16, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + }, \ +} + +struct ads1015_data { + struct regmap *regmap; + /* + * Protects ADC ops, e.g: concurrent sysfs/buffered + * data reads, configuration updates + */ + struct mutex lock; + struct ads1015_channel_data channel_data[ADS1015_CHANNELS]; +}; + +static bool ads1015_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return (reg == ADS1015_CFG_REG); +} + +static const struct regmap_config ads1015_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = ADS1015_CFG_REG, + .writeable_reg = ads1015_is_writeable_reg, +}; + +static const struct iio_chan_spec ads1015_channels[] = { + ADS1015_V_DIFF_CHAN(0, 1, ADS1015_AIN0_AIN1), + ADS1015_V_DIFF_CHAN(0, 3, ADS1015_AIN0_AIN3), + ADS1015_V_DIFF_CHAN(1, 3, ADS1015_AIN1_AIN3), + ADS1015_V_DIFF_CHAN(2, 3, ADS1015_AIN2_AIN3), + ADS1015_V_CHAN(0, ADS1015_AIN0), + ADS1015_V_CHAN(1, ADS1015_AIN1), + ADS1015_V_CHAN(2, ADS1015_AIN2), + ADS1015_V_CHAN(3, ADS1015_AIN3), + IIO_CHAN_SOFT_TIMESTAMP(ADS1015_TIMESTAMP), +}; + +static int ads1015_set_power_state(struct ads1015_data *data, bool on) +{ + int ret; + struct device *dev = regmap_get_device(data->regmap); + + if (on) { + ret = pm_runtime_get_sync(dev); + if (ret < 0) + pm_runtime_put_noidle(dev); + } else { + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); + } + + return ret; +} + +static +int ads1015_get_adc_result(struct ads1015_data *data, int chan, int *val) +{ + int ret, pga, dr, conv_time; + bool change; + + if (chan < 0 || chan >= ADS1015_CHANNELS) + return -EINVAL; + + pga = data->channel_data[chan].pga; + dr = data->channel_data[chan].data_rate; + + ret = regmap_update_bits_check(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_MUX_MASK | + ADS1015_CFG_PGA_MASK, + chan << ADS1015_CFG_MUX_SHIFT | + pga << ADS1015_CFG_PGA_SHIFT, + &change); + if (ret < 0) + return ret; + + if (change) { + conv_time = DIV_ROUND_UP(USEC_PER_SEC, ads1015_data_rate[dr]); + usleep_range(conv_time, conv_time + 1); + } + + return regmap_read(data->regmap, ADS1015_CONV_REG, val); +} + +static irqreturn_t ads1015_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1015_data *data = iio_priv(indio_dev); + s16 buf[8]; /* 1x s16 ADC val + 3x s16 padding + 4x s16 timestamp */ + int chan, ret, res; + + memset(buf, 0, sizeof(buf)); + + mutex_lock(&data->lock); + chan = find_first_bit(indio_dev->active_scan_mask, + indio_dev->masklength); + ret = ads1015_get_adc_result(data, chan, &res); + if (ret < 0) { + mutex_unlock(&data->lock); + goto err; + } + + buf[0] = res; + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1015_set_scale(struct ads1015_data *data, int chan, + int scale, int uscale) +{ + int i, ret, rindex = -1; + + for (i = 0; i < ARRAY_SIZE(ads1015_scale); i++) + if (ads1015_scale[i].scale == scale && + ads1015_scale[i].uscale == uscale) { + rindex = i; + break; + } + if (rindex < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_PGA_MASK, + rindex << ADS1015_CFG_PGA_SHIFT); + if (ret < 0) + return ret; + + data->channel_data[chan].pga = rindex; + + return 0; +} + +static int ads1015_set_data_rate(struct ads1015_data *data, int chan, int rate) +{ + int i, ret, rindex = -1; + + for (i = 0; i < ARRAY_SIZE(ads1015_data_rate); i++) + if (ads1015_data_rate[i] == rate) { + rindex = i; + break; + } + if (rindex < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_DR_MASK, + rindex << ADS1015_CFG_DR_SHIFT); + if (ret < 0) + return ret; + + data->channel_data[chan].data_rate = rindex; + + return 0; +} + +static int ads1015_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret, idx; + struct ads1015_data *data = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + mutex_lock(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (iio_buffer_enabled(indio_dev)) { + ret = -EBUSY; + break; + } + + ret = ads1015_set_power_state(data, true); + if (ret < 0) + break; + + ret = ads1015_get_adc_result(data, chan->address, val); + if (ret < 0) { + ads1015_set_power_state(data, false); + break; + } + + /* 12 bit res, D0 is bit 4 in conversion register */ + *val = sign_extend32(*val >> 4, 11); + + ret = ads1015_set_power_state(data, false); + if (ret < 0) + break; + + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + idx = data->channel_data[chan->address].pga; + *val = ads1015_scale[idx].scale; + *val2 = ads1015_scale[idx].uscale; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + idx = data->channel_data[chan->address].data_rate; + *val = ads1015_data_rate[idx]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ads1015_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1015_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + ret = ads1015_set_scale(data, chan->address, val, val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = ads1015_set_data_rate(data, chan->address, val); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + + return ret; +} + +static int ads1015_buffer_preenable(struct iio_dev *indio_dev) +{ + return ads1015_set_power_state(iio_priv(indio_dev), true); +} + +static int ads1015_buffer_postdisable(struct iio_dev *indio_dev) +{ + return ads1015_set_power_state(iio_priv(indio_dev), false); +} + +static const struct iio_buffer_setup_ops ads1015_buffer_setup_ops = { + .preenable = ads1015_buffer_preenable, + .postenable = iio_triggered_buffer_postenable, + .predisable = iio_triggered_buffer_predisable, + .postdisable = ads1015_buffer_postdisable, + .validate_scan_mask = &iio_validate_scan_mask_onehot, +}; + +static IIO_CONST_ATTR(scale_available, "3 2 1 0.5 0.25 0.125"); +static IIO_CONST_ATTR(sampling_frequency_available, + "128 250 490 920 1600 2400 3300"); + +static struct attribute *ads1015_attributes[] = { + &iio_const_attr_scale_available.dev_attr.attr, + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ads1015_attribute_group = { + .attrs = ads1015_attributes, +}; + +static const struct iio_info ads1015_info = { + .driver_module = THIS_MODULE, + .read_raw = ads1015_read_raw, + .write_raw = ads1015_write_raw, + .attrs = &ads1015_attribute_group, +}; + +#ifdef CONFIG_OF +static int ads1015_get_channels_config_of(struct i2c_client *client) +{ + struct ads1015_data *data = i2c_get_clientdata(client); + struct device_node *node; + + if (!client->dev.of_node || + !of_get_next_child(client->dev.of_node, NULL)) + return -EINVAL; + + for_each_child_of_node(client->dev.of_node, node) { + u32 pval; + unsigned int channel; + unsigned int pga = ADS1015_DEFAULT_PGA; + unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; + + if (of_property_read_u32(node, "reg", &pval)) { + dev_err(&client->dev, "invalid reg on %s\n", + node->full_name); + continue; + } + + channel = pval; + if (channel >= ADS1015_CHANNELS) { + dev_err(&client->dev, + "invalid channel index %d on %s\n", + channel, node->full_name); + continue; + } + + if (!of_property_read_u32(node, "ti,gain", &pval)) { + pga = pval; + if (pga > 6) { + dev_err(&client->dev, "invalid gain on %s\n", + node->full_name); + return -EINVAL; + } + } + + if (!of_property_read_u32(node, "ti,datarate", &pval)) { + data_rate = pval; + if (data_rate > 7) { + dev_err(&client->dev, + "invalid data_rate on %s\n", + node->full_name); + return -EINVAL; + } + } + + data->channel_data[channel].pga = pga; + data->channel_data[channel].data_rate = data_rate; + } + + return 0; +} +#endif + +static void ads1015_get_channels_config(struct i2c_client *client) +{ + unsigned int k; + + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ads1015_data *data = iio_priv(indio_dev); + struct ads1015_platform_data *pdata = dev_get_platdata(&client->dev); + + /* prefer platform data */ + if (pdata) { + memcpy(data->channel_data, pdata->channel_data, + sizeof(data->channel_data)); + return; + } + +#ifdef CONFIG_OF + if (!ads1015_get_channels_config_of(client)) + return; +#endif + /* fallback on default configuration */ + for (k = 0; k < ADS1015_CHANNELS; ++k) { + data->channel_data[k].pga = ADS1015_DEFAULT_PGA; + data->channel_data[k].data_rate = ADS1015_DEFAULT_DATA_RATE; + } +} + +static int ads1015_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct ads1015_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ads1015_info; + indio_dev->name = ADS1015_DRV_NAME; + indio_dev->channels = ads1015_channels; + indio_dev->num_channels = ARRAY_SIZE(ads1015_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + /* we need to keep this ABI the same as used by hwmon ADS1015 driver */ + ads1015_get_channels_config(client); + + data->regmap = devm_regmap_init_i2c(client, &ads1015_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "Failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ads1015_trigger_handler, + &ads1015_buffer_setup_ops); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto err_buffer_cleanup; + pm_runtime_set_autosuspend_delay(&client->dev, ADS1015_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_enable(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "Failed to register IIO device\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + + return ret; +} + +static int ads1015_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ads1015_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + iio_triggered_buffer_cleanup(indio_dev); + + /* power down single shot mode */ + return regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_MOD_MASK, + ADS1015_SINGLESHOT << ADS1015_CFG_MOD_SHIFT); +} + +#ifdef CONFIG_PM +static int ads1015_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ads1015_data *data = iio_priv(indio_dev); + + return regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_MOD_MASK, + ADS1015_SINGLESHOT << ADS1015_CFG_MOD_SHIFT); +} + +static int ads1015_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct ads1015_data *data = iio_priv(indio_dev); + + return regmap_update_bits(data->regmap, ADS1015_CFG_REG, + ADS1015_CFG_MOD_MASK, + ADS1015_CONTINUOUS << ADS1015_CFG_MOD_SHIFT); +} +#endif + +static const struct dev_pm_ops ads1015_pm_ops = { + SET_RUNTIME_PM_OPS(ads1015_runtime_suspend, + ads1015_runtime_resume, NULL) +}; + +static const struct i2c_device_id ads1015_id[] = { + {"ads1015", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ads1015_id); + +static struct i2c_driver ads1015_driver = { + .driver = { + .name = ADS1015_DRV_NAME, + .pm = &ads1015_pm_ops, + }, + .probe = ads1015_probe, + .remove = ads1015_remove, + .id_table = ads1015_id, +}; + +module_i2c_driver(ads1015_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Texas Instruments ADS1015 ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/adc/ti-ads8688.c b/drivers/iio/adc/ti-ads8688.c new file mode 100644 index 0000000..03e9070 --- /dev/null +++ b/drivers/iio/adc/ti-ads8688.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2015 Prevas A/S + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ADS8688_CMD_REG(x) (x << 8) +#define ADS8688_CMD_REG_NOOP 0x00 +#define ADS8688_CMD_REG_RST 0x85 +#define ADS8688_CMD_REG_MAN_CH(chan) (0xC0 | (4 * chan)) +#define ADS8688_CMD_DONT_CARE_BITS 16 + +#define ADS8688_PROG_REG(x) (x << 9) +#define ADS8688_PROG_REG_RANGE_CH(chan) (0x05 + chan) +#define ADS8688_PROG_WR_BIT BIT(8) +#define ADS8688_PROG_DONT_CARE_BITS 8 + +#define ADS8688_REG_PLUSMINUS25VREF 0 +#define ADS8688_REG_PLUSMINUS125VREF 1 +#define ADS8688_REG_PLUSMINUS0625VREF 2 +#define ADS8688_REG_PLUS25VREF 5 +#define ADS8688_REG_PLUS125VREF 6 + +#define ADS8688_VREF_MV 4096 +#define ADS8688_REALBITS 16 + +/* + * enum ads8688_range - ADS8688 reference voltage range + * @ADS8688_PLUSMINUS25VREF: Device is configured for input range ±2.5 * VREF + * @ADS8688_PLUSMINUS125VREF: Device is configured for input range ±1.25 * VREF + * @ADS8688_PLUSMINUS0625VREF: Device is configured for input range ±0.625 * VREF + * @ADS8688_PLUS25VREF: Device is configured for input range 0 - 2.5 * VREF + * @ADS8688_PLUS125VREF: Device is configured for input range 0 - 1.25 * VREF + */ +enum ads8688_range { + ADS8688_PLUSMINUS25VREF, + ADS8688_PLUSMINUS125VREF, + ADS8688_PLUSMINUS0625VREF, + ADS8688_PLUS25VREF, + ADS8688_PLUS125VREF, +}; + +struct ads8688_chip_info { + const struct iio_chan_spec *channels; + unsigned int num_channels; +}; + +struct ads8688_state { + struct mutex lock; + const struct ads8688_chip_info *chip_info; + struct spi_device *spi; + struct regulator *reg; + unsigned int vref_mv; + enum ads8688_range range[8]; + union { + __be32 d32; + u8 d8[4]; + } data[2] ____cacheline_aligned; +}; + +enum ads8688_id { + ID_ADS8684, + ID_ADS8688, +}; + +struct ads8688_ranges { + enum ads8688_range range; + unsigned int scale; + int offset; + u8 reg; +}; + +static const struct ads8688_ranges ads8688_range_def[5] = { + { + .range = ADS8688_PLUSMINUS25VREF, + .scale = 76295, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS25VREF, + }, { + .range = ADS8688_PLUSMINUS125VREF, + .scale = 38148, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS125VREF, + }, { + .range = ADS8688_PLUSMINUS0625VREF, + .scale = 19074, + .offset = -(1 << (ADS8688_REALBITS - 1)), + .reg = ADS8688_REG_PLUSMINUS0625VREF, + }, { + .range = ADS8688_PLUS25VREF, + .scale = 38148, + .offset = 0, + .reg = ADS8688_REG_PLUS25VREF, + }, { + .range = ADS8688_PLUS125VREF, + .scale = 19074, + .offset = 0, + .reg = ADS8688_REG_PLUS125VREF, + } +}; + +static ssize_t ads8688_show_scales(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ads8688_state *st = iio_priv(dev_to_iio_dev(dev)); + + return sprintf(buf, "0.%09u 0.%09u 0.%09u\n", + ads8688_range_def[0].scale * st->vref_mv, + ads8688_range_def[1].scale * st->vref_mv, + ads8688_range_def[2].scale * st->vref_mv); +} + +static ssize_t ads8688_show_offsets(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d %d\n", ads8688_range_def[0].offset, + ads8688_range_def[3].offset); +} + +static IIO_DEVICE_ATTR(in_voltage_scale_available, S_IRUGO, + ads8688_show_scales, NULL, 0); +static IIO_DEVICE_ATTR(in_voltage_offset_available, S_IRUGO, + ads8688_show_offsets, NULL, 0); + +static struct attribute *ads8688_attributes[] = { + &iio_dev_attr_in_voltage_scale_available.dev_attr.attr, + &iio_dev_attr_in_voltage_offset_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ads8688_attribute_group = { + .attrs = ads8688_attributes, +}; + +#define ADS8688_CHAN(index) \ +{ \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SCALE) \ + | BIT(IIO_CHAN_INFO_OFFSET), \ +} + +static const struct iio_chan_spec ads8684_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), +}; + +static const struct iio_chan_spec ads8688_channels[] = { + ADS8688_CHAN(0), + ADS8688_CHAN(1), + ADS8688_CHAN(2), + ADS8688_CHAN(3), + ADS8688_CHAN(4), + ADS8688_CHAN(5), + ADS8688_CHAN(6), + ADS8688_CHAN(7), +}; + +static int ads8688_prog_write(struct iio_dev *indio_dev, unsigned int addr, + unsigned int val) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_PROG_REG(addr) | ADS8688_PROG_WR_BIT | val; + tmp <<= ADS8688_PROG_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ads8688_reset(struct iio_dev *indio_dev) +{ + struct ads8688_state *st = iio_priv(indio_dev); + u32 tmp; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_RST); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + return spi_write(st->spi, &st->data[0].d8[0], 4); +} + +static int ads8688_read(struct iio_dev *indio_dev, unsigned int chan) +{ + struct ads8688_state *st = iio_priv(indio_dev); + int ret; + u32 tmp; + struct spi_transfer t[] = { + { + .tx_buf = &st->data[0].d8[0], + .len = 4, + .cs_change = 1, + }, { + .tx_buf = &st->data[1].d8[0], + .rx_buf = &st->data[1].d8[0], + .len = 4, + }, + }; + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_MAN_CH(chan)); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[0].d32 = cpu_to_be32(tmp); + + tmp = ADS8688_CMD_REG(ADS8688_CMD_REG_NOOP); + tmp <<= ADS8688_CMD_DONT_CARE_BITS; + st->data[1].d32 = cpu_to_be32(tmp); + + ret = spi_sync_transfer(st->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return be32_to_cpu(st->data[1].d32) & 0xffff; +} + +static int ads8688_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + int ret, offset; + unsigned long scale_mv; + + struct ads8688_state *st = iio_priv(indio_dev); + + mutex_lock(&st->lock); + switch (m) { + case IIO_CHAN_INFO_RAW: + ret = ads8688_read(indio_dev, chan->channel); + mutex_unlock(&st->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + scale_mv = st->vref_mv; + scale_mv *= ads8688_range_def[st->range[chan->channel]].scale; + *val = 0; + *val2 = scale_mv; + mutex_unlock(&st->lock); + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + offset = ads8688_range_def[st->range[chan->channel]].offset; + *val = offset; + mutex_unlock(&st->lock); + return IIO_VAL_INT; + } + mutex_unlock(&st->lock); + + return -EINVAL; +} + +static int ads8688_write_reg_range(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + enum ads8688_range range) +{ + unsigned int tmp; + int ret; + + tmp = ADS8688_PROG_REG_RANGE_CH(chan->channel); + ret = ads8688_prog_write(indio_dev, tmp, range); + + return ret; +} + +static int ads8688_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads8688_state *st = iio_priv(indio_dev); + unsigned int scale = 0; + int ret = -EINVAL, i, offset = 0; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_SCALE: + /* If the offset is 0 the ±2.5 * VREF mode is not available */ + offset = ads8688_range_def[st->range[chan->channel]].offset; + if (offset == 0 && val2 == ads8688_range_def[0].scale * st->vref_mv) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val2 == ads8688_range_def[i].scale * st->vref_mv && + offset == ads8688_range_def[i].offset) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* + * There are only two available offsets: + * 0 and -(1 << (ADS8688_REALBITS - 1)) + */ + if (!(ads8688_range_def[0].offset == val || + ads8688_range_def[3].offset == val)) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + /* + * If the device are in ±2.5 * VREF mode, it's not allowed to + * switch to a mode where the offset is 0 + */ + if (val == 0 && + st->range[chan->channel] == ADS8688_PLUSMINUS25VREF) { + mutex_unlock(&st->lock); + return -EINVAL; + } + + scale = ads8688_range_def[st->range[chan->channel]].scale; + + /* Lookup new mode */ + for (i = 0; i < ARRAY_SIZE(ads8688_range_def); i++) + if (val == ads8688_range_def[i].offset && + scale == ads8688_range_def[i].scale) { + ret = ads8688_write_reg_range(indio_dev, chan, + ads8688_range_def[i].reg); + break; + } + break; + } + + if (!ret) + st->range[chan->channel] = ads8688_range_def[i].range; + + mutex_unlock(&st->lock); + + return ret; +} + +static int ads8688_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info ads8688_info = { + .read_raw = &ads8688_read_raw, + .write_raw = &ads8688_write_raw, + .write_raw_get_fmt = &ads8688_write_raw_get_fmt, + .attrs = &ads8688_attribute_group, + .driver_module = THIS_MODULE, +}; + +static const struct ads8688_chip_info ads8688_chip_info_tbl[] = { + [ID_ADS8684] = { + .channels = ads8684_channels, + .num_channels = ARRAY_SIZE(ads8684_channels), + }, + [ID_ADS8688] = { + .channels = ads8688_channels, + .num_channels = ARRAY_SIZE(ads8688_channels), + }, +}; + +static int ads8688_probe(struct spi_device *spi) +{ + struct ads8688_state *st; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (indio_dev == NULL) + return -ENOMEM; + + st = iio_priv(indio_dev); + + st->reg = devm_regulator_get_optional(&spi->dev, "vref"); + if (!IS_ERR(st->reg)) { + ret = regulator_enable(st->reg); + if (ret) + return ret; + + ret = regulator_get_voltage(st->reg); + if (ret < 0) + goto error_out; + + st->vref_mv = ret / 1000; + } else { + /* Use internal reference */ + st->vref_mv = ADS8688_VREF_MV; + } + + st->chip_info = &ads8688_chip_info_tbl[spi_get_device_id(spi)->driver_data]; + + spi->mode = SPI_MODE_1; + + spi_set_drvdata(spi, indio_dev); + + st->spi = spi; + + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->dev.parent = &spi->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = st->chip_info->channels; + indio_dev->num_channels = st->chip_info->num_channels; + indio_dev->info = &ads8688_info; + + ads8688_reset(indio_dev); + + mutex_init(&st->lock); + + ret = iio_device_register(indio_dev); + if (ret) + goto error_out; + + return 0; + +error_out: + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return ret; +} + +static int ads8688_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct ads8688_state *st = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + if (!IS_ERR_OR_NULL(st->reg)) + regulator_disable(st->reg); + + return 0; +} + +static const struct spi_device_id ads8688_id[] = { + {"ads8684", ID_ADS8684}, + {"ads8688", ID_ADS8688}, + {} +}; +MODULE_DEVICE_TABLE(spi, ads8688_id); + +static const struct of_device_id ads8688_of_match[] = { + { .compatible = "ti,ads8684" }, + { .compatible = "ti,ads8688" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads8688_of_match); + +static struct spi_driver ads8688_driver = { + .driver = { + .name = "ads8688", + .owner = THIS_MODULE, + }, + .probe = ads8688_probe, + .remove = ads8688_remove, + .id_table = ads8688_id, +}; +module_spi_driver(ads8688_driver); + +MODULE_AUTHOR("Sean Nyekjaer "); +MODULE_DESCRIPTION("Texas Instruments ADS8688 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/vf610_adc.c b/drivers/iio/adc/vf610_adc.c index b10f629..653bf13 100644 --- a/drivers/iio/adc/vf610_adc.c +++ b/drivers/iio/adc/vf610_adc.c @@ -714,19 +714,19 @@ static int vf610_write_raw(struct iio_dev *indio_dev, int i; switch (mask) { - case IIO_CHAN_INFO_SAMP_FREQ: - for (i = 0; - i < ARRAY_SIZE(info->sample_freq_avail); - i++) - if (val == info->sample_freq_avail[i]) { - info->adc_feature.sample_rate = i; - vf610_adc_sample_set(info); - return 0; - } - break; + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; + i < ARRAY_SIZE(info->sample_freq_avail); + i++) + if (val == info->sample_freq_avail[i]) { + info->adc_feature.sample_rate = i; + vf610_adc_sample_set(info); + return 0; + } + break; - default: - break; + default: + break; } return -EINVAL; diff --git a/drivers/iio/adc/xilinx-xadc-core.c b/drivers/iio/adc/xilinx-xadc-core.c index 02e636a..0a6beb3 100644 --- a/drivers/iio/adc/xilinx-xadc-core.c +++ b/drivers/iio/adc/xilinx-xadc-core.c @@ -803,7 +803,7 @@ err: return ret; } -static struct iio_buffer_setup_ops xadc_buffer_ops = { +static const struct iio_buffer_setup_ops xadc_buffer_ops = { .preenable = &xadc_preenable, .postenable = &iio_triggered_buffer_postenable, .predisable = &iio_triggered_buffer_predisable, diff --git a/drivers/iio/buffer/Kconfig b/drivers/iio/buffer/Kconfig index 0a7b2fd..4ffd3db 100644 --- a/drivers/iio/buffer/Kconfig +++ b/drivers/iio/buffer/Kconfig @@ -9,6 +9,26 @@ config IIO_BUFFER_CB Should be selected by any drivers that do in-kernel push usage. That is, those where the data is pushed to the consumer. +config IIO_BUFFER_DMA + tristate + help + Provides the generic IIO DMA buffer infrastructure that can be used by + drivers for devices with DMA support to implement the IIO buffer. + + Should be selected by drivers that want to use the generic DMA buffer + infrastructure. + +config IIO_BUFFER_DMAENGINE + tristate + select IIO_BUFFER_DMA + help + Provides a bonding of the generic IIO DMA buffer infrastructure with the + DMAengine framework. This can be used by converter drivers with a DMA port + connected to an external DMA controller which is supported by the + DMAengine framework. + + Should be selected by drivers that want to use this functionality. + config IIO_KFIFO_BUF tristate "Industrial I/O buffering based on kfifo" help diff --git a/drivers/iio/buffer/Makefile b/drivers/iio/buffer/Makefile index 4d193b9..85beaae 100644 --- a/drivers/iio/buffer/Makefile +++ b/drivers/iio/buffer/Makefile @@ -4,5 +4,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_IIO_BUFFER_CB) += industrialio-buffer-cb.o +obj-$(CONFIG_IIO_BUFFER_DMA) += industrialio-buffer-dma.o +obj-$(CONFIG_IIO_BUFFER_DMAENGINE) += industrialio-buffer-dmaengine.o obj-$(CONFIG_IIO_TRIGGERED_BUFFER) += industrialio-triggered-buffer.o obj-$(CONFIG_IIO_KFIFO_BUF) += kfifo_buf.o diff --git b/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c new file mode 100644 index 0000000..212cbed --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dma.c @@ -0,0 +1,683 @@ +/* + * Copyright 2013-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * For DMA buffers the storage is sub-divided into so called blocks. Each block + * has its own memory buffer. The size of the block is the granularity at which + * memory is exchanged between the hardware and the application. Increasing the + * basic unit of data exchange from one sample to one block decreases the + * management overhead that is associated with each sample. E.g. if we say the + * management overhead for one exchange is x and the unit of exchange is one + * sample the overhead will be x for each sample. Whereas when using a block + * which contains n samples the overhead per sample is reduced to x/n. This + * allows to achieve much higher samplerates than what can be sustained with + * the one sample approach. + * + * Blocks are exchanged between the DMA controller and the application via the + * means of two queues. The incoming queue and the outgoing queue. Blocks on the + * incoming queue are waiting for the DMA controller to pick them up and fill + * them with data. Block on the outgoing queue have been filled with data and + * are waiting for the application to dequeue them and read the data. + * + * A block can be in one of the following states: + * * Owned by the application. In this state the application can read data from + * the block. + * * On the incoming list: Blocks on the incoming list are queued up to be + * processed by the DMA controller. + * * Owned by the DMA controller: The DMA controller is processing the block + * and filling it with data. + * * On the outgoing list: Blocks on the outgoing list have been successfully + * processed by the DMA controller and contain data. They can be dequeued by + * the application. + * * Dead: A block that is dead has been marked as to be freed. It might still + * be owned by either the application or the DMA controller at the moment. + * But once they are done processing it instead of going to either the + * incoming or outgoing queue the block will be freed. + * + * In addition to this blocks are reference counted and the memory associated + * with both the block structure as well as the storage memory for the block + * will be freed when the last reference to the block is dropped. This means a + * block must not be accessed without holding a reference. + * + * The iio_dma_buffer implementation provides a generic infrastructure for + * managing the blocks. + * + * A driver for a specific piece of hardware that has DMA capabilities need to + * implement the submit() callback from the iio_dma_buffer_ops structure. This + * callback is supposed to initiate the DMA transfer copying data from the + * converter to the memory region of the block. Once the DMA transfer has been + * completed the driver must call iio_dma_buffer_block_done() for the completed + * block. + * + * Prior to this it must set the bytes_used field of the block contains + * the actual number of bytes in the buffer. Typically this will be equal to the + * size of the block, but if the DMA hardware has certain alignment requirements + * for the transfer length it might choose to use less than the full size. In + * either case it is expected that bytes_used is a multiple of the bytes per + * datum, i.e. the block must not contain partial samples. + * + * The driver must call iio_dma_buffer_block_done() for each block it has + * received through its submit_block() callback, even if it does not actually + * perform a DMA transfer for the block, e.g. because the buffer was disabled + * before the block transfer was started. In this case it should set bytes_used + * to 0. + * + * In addition it is recommended that a driver implements the abort() callback. + * It will be called when the buffer is disabled and can be used to cancel + * pending and stop active transfers. + * + * The specific driver implementation should use the default callback + * implementations provided by this module for the iio_buffer_access_funcs + * struct. It may overload some callbacks with custom variants if the hardware + * has special requirements that are not handled by the generic functions. If a + * driver chooses to overload a callback it has to ensure that the generic + * callback is called from within the custom callback. + */ + +static void iio_buffer_block_release(struct kref *kref) +{ + struct iio_dma_buffer_block *block = container_of(kref, + struct iio_dma_buffer_block, kref); + + WARN_ON(block->state != IIO_BLOCK_STATE_DEAD); + + dma_free_coherent(block->queue->dev, PAGE_ALIGN(block->size), + block->vaddr, block->phys_addr); + + iio_buffer_put(&block->queue->buffer); + kfree(block); +} + +static void iio_buffer_block_get(struct iio_dma_buffer_block *block) +{ + kref_get(&block->kref); +} + +static void iio_buffer_block_put(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release); +} + +/* + * dma_free_coherent can sleep, hence we need to take some special care to be + * able to drop a reference from an atomic context. + */ +static LIST_HEAD(iio_dma_buffer_dead_blocks); +static DEFINE_SPINLOCK(iio_dma_buffer_dead_blocks_lock); + +static void iio_dma_buffer_cleanup_worker(struct work_struct *work) +{ + struct iio_dma_buffer_block *block, *_block; + LIST_HEAD(block_list); + + spin_lock_irq(&iio_dma_buffer_dead_blocks_lock); + list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list); + spin_unlock_irq(&iio_dma_buffer_dead_blocks_lock); + + list_for_each_entry_safe(block, _block, &block_list, head) + iio_buffer_block_release(&block->kref); +} +static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker); + +static void iio_buffer_block_release_atomic(struct kref *kref) +{ + struct iio_dma_buffer_block *block; + unsigned long flags; + + block = container_of(kref, struct iio_dma_buffer_block, kref); + + spin_lock_irqsave(&iio_dma_buffer_dead_blocks_lock, flags); + list_add_tail(&block->head, &iio_dma_buffer_dead_blocks); + spin_unlock_irqrestore(&iio_dma_buffer_dead_blocks_lock, flags); + + schedule_work(&iio_dma_buffer_cleanup_work); +} + +/* + * Version of iio_buffer_block_put() that can be called from atomic context + */ +static void iio_buffer_block_put_atomic(struct iio_dma_buffer_block *block) +{ + kref_put(&block->kref, iio_buffer_block_release_atomic); +} + +static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf) +{ + return container_of(buf, struct iio_dma_buffer_queue, buffer); +} + +static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block( + struct iio_dma_buffer_queue *queue, size_t size) +{ + struct iio_dma_buffer_block *block; + + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (!block) + return NULL; + + block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size), + &block->phys_addr, GFP_KERNEL); + if (!block->vaddr) { + kfree(block); + return NULL; + } + + block->size = size; + block->state = IIO_BLOCK_STATE_DEQUEUED; + block->queue = queue; + INIT_LIST_HEAD(&block->head); + kref_init(&block->kref); + + iio_buffer_get(&queue->buffer); + + return block; +} + +static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + + /* + * The buffer has already been freed by the application, just drop the + * reference. + */ + if (block->state != IIO_BLOCK_STATE_DEAD) { + block->state = IIO_BLOCK_STATE_DONE; + list_add_tail(&block->head, &queue->outgoing); + } +} + +/** + * iio_dma_buffer_block_done() - Indicate that a block has been completed + * @block: The completed block + * + * Should be called when the DMA controller has finished handling the block to + * pass back ownership of the block to the queue. + */ +void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block) +{ + struct iio_dma_buffer_queue *queue = block->queue; + unsigned long flags; + + spin_lock_irqsave(&queue->list_lock, flags); + _iio_dma_buffer_block_done(block); + spin_unlock_irqrestore(&queue->list_lock, flags); + + iio_buffer_block_put_atomic(block); + wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done); + +/** + * iio_dma_buffer_block_list_abort() - Indicate that a list block has been + * aborted + * @queue: Queue for which to complete blocks. + * @list: List of aborted blocks. All blocks in this list must be from @queue. + * + * Typically called from the abort() callback after the DMA controller has been + * stopped. This will set bytes_used to 0 for each block in the list and then + * hand the blocks back to the queue. + */ +void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue, + struct list_head *list) +{ + struct iio_dma_buffer_block *block, *_block; + unsigned long flags; + + spin_lock_irqsave(&queue->list_lock, flags); + list_for_each_entry_safe(block, _block, list, head) { + list_del(&block->head); + block->bytes_used = 0; + _iio_dma_buffer_block_done(block); + iio_buffer_block_put_atomic(block); + } + spin_unlock_irqrestore(&queue->list_lock, flags); + + wake_up_interruptible_poll(&queue->buffer.pollq, POLLIN | POLLRDNORM); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort); + +static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block) +{ + /* + * If the core owns the block it can be re-used. This should be the + * default case when enabling the buffer, unless the DMA controller does + * not support abort and has not given back the block yet. + */ + switch (block->state) { + case IIO_BLOCK_STATE_DEQUEUED: + case IIO_BLOCK_STATE_QUEUED: + case IIO_BLOCK_STATE_DONE: + return true; + default: + return false; + } +} + +/** + * iio_dma_buffer_request_update() - DMA buffer request_update callback + * @buffer: The buffer which to request an update + * + * Should be used as the iio_dma_buffer_request_update() callback for + * iio_buffer_access_ops struct for DMA buffers. + */ +int iio_dma_buffer_request_update(struct iio_buffer *buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + bool try_reuse = false; + size_t size; + int ret = 0; + int i; + + /* + * Split the buffer into two even parts. This is used as a double + * buffering scheme with usually one block at a time being used by the + * DMA and the other one by the application. + */ + size = DIV_ROUND_UP(queue->buffer.bytes_per_datum * + queue->buffer.length, 2); + + mutex_lock(&queue->lock); + + /* Allocations are page aligned */ + if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size)) + try_reuse = true; + + queue->fileio.block_size = size; + queue->fileio.active_block = NULL; + + spin_lock_irq(&queue->list_lock); + for (i = 0; i < 2; i++) { + block = queue->fileio.blocks[i]; + + /* If we can't re-use it free it */ + if (block && (!iio_dma_block_reusable(block) || !try_reuse)) + block->state = IIO_BLOCK_STATE_DEAD; + } + + /* + * At this point all blocks are either owned by the core or marked as + * dead. This means we can reset the lists without having to fear + * corrution. + */ + INIT_LIST_HEAD(&queue->outgoing); + spin_unlock_irq(&queue->list_lock); + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < 2; i++) { + if (queue->fileio.blocks[i]) { + block = queue->fileio.blocks[i]; + if (block->state == IIO_BLOCK_STATE_DEAD) { + /* Could not reuse it */ + iio_buffer_block_put(block); + block = NULL; + } else { + block->size = size; + } + } else { + block = NULL; + } + + if (!block) { + block = iio_dma_buffer_alloc_block(queue, size); + if (!block) { + ret = -ENOMEM; + goto out_unlock; + } + queue->fileio.blocks[i] = block; + } + + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } + +out_unlock: + mutex_unlock(&queue->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_request_update); + +static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + int ret; + + /* + * If the hardware has already been removed we put the block into + * limbo. It will neither be on the incoming nor outgoing list, nor will + * it ever complete. It will just wait to be freed eventually. + */ + if (!queue->ops) + return; + + block->state = IIO_BLOCK_STATE_ACTIVE; + iio_buffer_block_get(block); + ret = queue->ops->submit(queue, block); + if (ret) { + /* + * This is a bit of a problem and there is not much we can do + * other then wait for the buffer to be disabled and re-enabled + * and try again. But it should not really happen unless we run + * out of memory or something similar. + * + * TODO: Implement support in the IIO core to allow buffers to + * notify consumers that something went wrong and the buffer + * should be disabled. + */ + iio_buffer_block_put(block); + } +} + +/** + * iio_dma_buffer_enable() - Enable DMA buffer + * @buffer: IIO buffer to enable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to starts + * sampling. Typically should be the iio_buffer_access_ops enable callback. + * + * This will allocate the DMA buffers and start the DMA transfers. + */ +int iio_dma_buffer_enable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block, *_block; + + mutex_lock(&queue->lock); + queue->active = true; + list_for_each_entry_safe(block, _block, &queue->incoming, head) { + list_del(&block->head); + iio_dma_buffer_submit_block(queue, block); + } + mutex_unlock(&queue->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_enable); + +/** + * iio_dma_buffer_disable() - Disable DMA buffer + * @buffer: IIO DMA buffer to disable + * @indio_dev: IIO device the buffer is attached to + * + * Needs to be called when the device that the buffer is attached to stops + * sampling. Typically should be the iio_buffer_access_ops disable callback. + */ +int iio_dma_buffer_disable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + + mutex_lock(&queue->lock); + queue->active = false; + + if (queue->ops && queue->ops->abort) + queue->ops->abort(queue); + mutex_unlock(&queue->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_disable); + +static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + if (block->state == IIO_BLOCK_STATE_DEAD) { + iio_buffer_block_put(block); + } else if (queue->active) { + iio_dma_buffer_submit_block(queue, block); + } else { + block->state = IIO_BLOCK_STATE_QUEUED; + list_add_tail(&block->head, &queue->incoming); + } +} + +static struct iio_dma_buffer_block *iio_dma_buffer_dequeue( + struct iio_dma_buffer_queue *queue) +{ + struct iio_dma_buffer_block *block; + + spin_lock_irq(&queue->list_lock); + block = list_first_entry_or_null(&queue->outgoing, struct + iio_dma_buffer_block, head); + if (block != NULL) { + list_del(&block->head); + block->state = IIO_BLOCK_STATE_DEQUEUED; + } + spin_unlock_irq(&queue->list_lock); + + return block; +} + +/** + * iio_dma_buffer_read() - DMA buffer read callback + * @buffer: Buffer to read form + * @n: Number of bytes to read + * @user_buffer: Userspace buffer to copy the data to + * + * Should be used as the read_first_n callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n, + char __user *user_buffer) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer); + struct iio_dma_buffer_block *block; + int ret; + + if (n < buffer->bytes_per_datum) + return -EINVAL; + + mutex_lock(&queue->lock); + + if (!queue->fileio.active_block) { + block = iio_dma_buffer_dequeue(queue); + if (block == NULL) { + ret = 0; + goto out_unlock; + } + queue->fileio.pos = 0; + queue->fileio.active_block = block; + } else { + block = queue->fileio.active_block; + } + + n = rounddown(n, buffer->bytes_per_datum); + if (n > block->bytes_used - queue->fileio.pos) + n = block->bytes_used - queue->fileio.pos; + + if (copy_to_user(user_buffer, block->vaddr + queue->fileio.pos, n)) { + ret = -EFAULT; + goto out_unlock; + } + + queue->fileio.pos += n; + + if (queue->fileio.pos == block->bytes_used) { + queue->fileio.active_block = NULL; + iio_dma_buffer_enqueue(queue, block); + } + + ret = n; + +out_unlock: + mutex_unlock(&queue->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_read); + +/** + * iio_dma_buffer_data_available() - DMA buffer data_available callback + * @buf: Buffer to check for data availability + * + * Should be used as the data_available callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +size_t iio_dma_buffer_data_available(struct iio_buffer *buf) +{ + struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf); + struct iio_dma_buffer_block *block; + size_t data_available = 0; + + /* + * For counting the available bytes we'll use the size of the block not + * the number of actual bytes available in the block. Otherwise it is + * possible that we end up with a value that is lower than the watermark + * but won't increase since all blocks are in use. + */ + + mutex_lock(&queue->lock); + if (queue->fileio.active_block) + data_available += queue->fileio.active_block->size; + + spin_lock_irq(&queue->list_lock); + list_for_each_entry(block, &queue->outgoing, head) + data_available += block->size; + spin_unlock_irq(&queue->list_lock); + mutex_unlock(&queue->lock); + + return data_available; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available); + +/** + * iio_dma_buffer_set_bytes_per_datum() - DMA buffer set_bytes_per_datum callback + * @buffer: Buffer to set the bytes-per-datum for + * @bpd: The new bytes-per-datum value + * + * Should be used as the set_bytes_per_datum callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd) +{ + buffer->bytes_per_datum = bpd; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_set_bytes_per_datum); + +/** + * iio_dma_buffer_set_length - DMA buffer set_length callback + * @buffer: Buffer to set the length for + * @length: The new buffer length + * + * Should be used as the set_length callback for iio_buffer_access_ops + * struct for DMA buffers. + */ +int iio_dma_buffer_set_length(struct iio_buffer *buffer, int length) +{ + /* Avoid an invalid state */ + if (length < 2) + length = 2; + buffer->length = length; + buffer->watermark = length / 2; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_set_length); + +/** + * iio_dma_buffer_init() - Initialize DMA buffer queue + * @queue: Buffer to initialize + * @dev: DMA device + * @ops: DMA buffer queue callback operations + * + * The DMA device will be used by the queue to do DMA memory allocations. So it + * should refer to the device that will perform the DMA to ensure that + * allocations are done from a memory region that can be accessed by the device. + */ +int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, + struct device *dev, const struct iio_dma_buffer_ops *ops) +{ + iio_buffer_init(&queue->buffer); + queue->buffer.length = PAGE_SIZE; + queue->buffer.watermark = queue->buffer.length / 2; + queue->dev = dev; + queue->ops = ops; + + INIT_LIST_HEAD(&queue->incoming); + INIT_LIST_HEAD(&queue->outgoing); + + mutex_init(&queue->lock); + spin_lock_init(&queue->list_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_init); + +/** + * iio_dma_buffer_exit() - Cleanup DMA buffer queue + * @queue: Buffer to cleanup + * + * After this function has completed it is safe to free any resources that are + * associated with the buffer and are accessed inside the callback operations. + */ +void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue) +{ + unsigned int i; + + mutex_lock(&queue->lock); + + spin_lock_irq(&queue->list_lock); + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD; + } + INIT_LIST_HEAD(&queue->outgoing); + spin_unlock_irq(&queue->list_lock); + + INIT_LIST_HEAD(&queue->incoming); + + for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) { + if (!queue->fileio.blocks[i]) + continue; + iio_buffer_block_put(queue->fileio.blocks[i]); + queue->fileio.blocks[i] = NULL; + } + queue->fileio.active_block = NULL; + queue->ops = NULL; + + mutex_unlock(&queue->lock); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_exit); + +/** + * iio_dma_buffer_release() - Release final buffer resources + * @queue: Buffer to release + * + * Frees resources that can't yet be freed in iio_dma_buffer_exit(). Should be + * called in the buffers release callback implementation right before freeing + * the memory associated with the buffer. + */ +void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue) +{ + mutex_destroy(&queue->lock); +} +EXPORT_SYMBOL_GPL(iio_dma_buffer_release); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("DMA buffer for the IIO framework"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c new file mode 100644 index 0000000..9fabed4 --- /dev/null +++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c @@ -0,0 +1,208 @@ +/* + * Copyright 2014-2015 Analog Devices Inc. + * Author: Lars-Peter Clausen + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * The IIO DMAengine buffer combines the generic IIO DMA buffer infrastructure + * with the DMAengine framework. The generic IIO DMA buffer infrastructure is + * used to manage the buffer memory and implement the IIO buffer operations + * while the DMAengine framework is used to perform the DMA transfers. Combined + * this results in a device independent fully functional DMA buffer + * implementation that can be used by device drivers for peripherals which are + * connected to a DMA controller which has a DMAengine driver implementation. + */ + +struct dmaengine_buffer { + struct iio_dma_buffer_queue queue; + + struct dma_chan *chan; + struct list_head active; + + size_t align; + size_t max_size; +}; + +static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer( + struct iio_buffer *buffer) +{ + return container_of(buffer, struct dmaengine_buffer, queue.buffer); +} + +static void iio_dmaengine_buffer_block_done(void *data) +{ + struct iio_dma_buffer_block *block = data; + unsigned long flags; + + spin_lock_irqsave(&block->queue->list_lock, flags); + list_del(&block->head); + spin_unlock_irqrestore(&block->queue->list_lock, flags); + iio_dma_buffer_block_done(block); +} + +static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue, + struct iio_dma_buffer_block *block) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + + block->bytes_used = min(block->size, dmaengine_buffer->max_size); + block->bytes_used = rounddown(block->bytes_used, + dmaengine_buffer->align); + + desc = dmaengine_prep_slave_single(dmaengine_buffer->chan, + block->phys_addr, block->bytes_used, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) + return -ENOMEM; + + desc->callback = iio_dmaengine_buffer_block_done; + desc->callback_param = block; + + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) + return dma_submit_error(cookie); + + spin_lock_irq(&dmaengine_buffer->queue.list_lock); + list_add_tail(&block->head, &dmaengine_buffer->active); + spin_unlock_irq(&dmaengine_buffer->queue.list_lock); + + dma_async_issue_pending(dmaengine_buffer->chan); + + return 0; +} + +static void iio_dmaengine_buffer_abort(struct iio_dma_buffer_queue *queue) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(&queue->buffer); + + dmaengine_terminate_sync(dmaengine_buffer->chan); + iio_dma_buffer_block_list_abort(queue, &dmaengine_buffer->active); +} + +static void iio_dmaengine_buffer_release(struct iio_buffer *buf) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buf); + + iio_dma_buffer_release(&dmaengine_buffer->queue); + kfree(dmaengine_buffer); +} + +static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = { + .read_first_n = iio_dma_buffer_read, + .set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum, + .set_length = iio_dma_buffer_set_length, + .request_update = iio_dma_buffer_request_update, + .enable = iio_dma_buffer_enable, + .disable = iio_dma_buffer_disable, + .data_available = iio_dma_buffer_data_available, + .release = iio_dmaengine_buffer_release, + + .modes = INDIO_BUFFER_HARDWARE, + .flags = INDIO_BUFFER_FLAG_FIXED_WATERMARK, +}; + +static const struct iio_dma_buffer_ops iio_dmaengine_default_ops = { + .submit = iio_dmaengine_buffer_submit_block, + .abort = iio_dmaengine_buffer_abort, +}; + +/** + * iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine + * @dev: Parent device for the buffer + * @channel: DMA channel name, typically "rx". + * + * This allocates a new IIO buffer which internally uses the DMAengine framework + * to perform its transfers. The parent device will be used to request the DMA + * channel. + * + * Once done using the buffer iio_dmaengine_buffer_free() should be used to + * release it. + */ +struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev, + const char *channel) +{ + struct dmaengine_buffer *dmaengine_buffer; + unsigned int width, src_width, dest_width; + struct dma_slave_caps caps; + struct dma_chan *chan; + int ret; + + dmaengine_buffer = kzalloc(sizeof(*dmaengine_buffer), GFP_KERNEL); + if (!dmaengine_buffer) + return ERR_PTR(-ENOMEM); + + chan = dma_request_slave_channel_reason(dev, channel); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + goto err_free; + } + + ret = dma_get_slave_caps(chan, &caps); + if (ret < 0) + goto err_free; + + /* Needs to be aligned to the maximum of the minimums */ + if (caps.src_addr_widths) + src_width = __ffs(caps.src_addr_widths); + else + src_width = 1; + if (caps.dst_addr_widths) + dest_width = __ffs(caps.dst_addr_widths); + else + dest_width = 1; + width = max(src_width, dest_width); + + INIT_LIST_HEAD(&dmaengine_buffer->active); + dmaengine_buffer->chan = chan; + dmaengine_buffer->align = width; + dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev); + + iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev, + &iio_dmaengine_default_ops); + + dmaengine_buffer->queue.buffer.access = &iio_dmaengine_buffer_ops; + + return &dmaengine_buffer->queue.buffer; + +err_free: + kfree(dmaengine_buffer); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(iio_dmaengine_buffer_alloc); + +/** + * iio_dmaengine_buffer_free() - Free dmaengine buffer + * @buffer: Buffer to free + * + * Frees a buffer previously allocated with iio_dmaengine_buffer_alloc(). + */ +void iio_dmaengine_buffer_free(struct iio_buffer *buffer) +{ + struct dmaengine_buffer *dmaengine_buffer = + iio_buffer_to_dmaengine_buffer(buffer); + + iio_dma_buffer_exit(&dmaengine_buffer->queue); + dma_release_channel(dmaengine_buffer->chan); + + iio_buffer_put(buffer); +} +EXPORT_SYMBOL_GPL(iio_dmaengine_buffer_free); diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig index 3061b72..f73290f 100644 --- a/drivers/iio/chemical/Kconfig +++ b/drivers/iio/chemical/Kconfig @@ -4,6 +4,28 @@ menu "Chemical Sensors" +config ATLAS_PH_SENSOR + tristate "Atlas Scientific OEM pH-SM sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + select IRQ_WORK + help + Say Y here to build I2C interface support for the Atlas + Scientific OEM pH-SM sensor. + + To compile this driver as module, choose M here: the + module will be called atlas-ph-sensor. + +config IAQCORE + tristate "AMS iAQ-Core VOC sensors" + depends on I2C + help + Say Y here to build I2C interface support for the AMS + iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds) + sensors + config VZ89X tristate "SGX Sensortech MiCS VZ89X VOC sensor" depends on I2C diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile index 7292f2d..b02202b 100644 --- a/drivers/iio/chemical/Makefile +++ b/drivers/iio/chemical/Makefile @@ -3,4 +3,6 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-ph-sensor.o +obj-$(CONFIG_IAQCORE) += ams-iaq-core.o obj-$(CONFIG_VZ89X) += vz89x.o diff --git b/drivers/iio/chemical/ams-iaq-core.c b/drivers/iio/chemical/ams-iaq-core.c new file mode 100644 index 0000000..41a8e6f --- /dev/null +++ b/drivers/iio/chemical/ams-iaq-core.c @@ -0,0 +1,200 @@ +/* + * ams-iaq-core.c - Support for AMS iAQ-Core VOC sensors + * + * Copyright (C) 2015 Matt Ranostay + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +#define AMS_IAQCORE_DATA_SIZE 9 + +#define AMS_IAQCORE_VOC_CO2_IDX 0 +#define AMS_IAQCORE_VOC_RESISTANCE_IDX 1 +#define AMS_IAQCORE_VOC_TVOC_IDX 2 + +struct ams_iaqcore_reading { + __be16 co2_ppm; + u8 status; + __be32 resistance; + __be16 voc_ppb; +} __attribute__((__packed__)); + +struct ams_iaqcore_data { + struct i2c_client *client; + struct mutex lock; + unsigned long last_update; + + struct ams_iaqcore_reading buffer; +}; + +static const struct iio_chan_spec ams_iaqcore_channels[] = { + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_CO2, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_CO2_IDX, + }, + { + .type = IIO_RESISTANCE, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_RESISTANCE_IDX, + }, + { + .type = IIO_CONCENTRATION, + .channel2 = IIO_MOD_VOC, + .modified = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .address = AMS_IAQCORE_VOC_TVOC_IDX, + }, +}; + +static int ams_iaqcore_read_measurement(struct ams_iaqcore_data *data) +{ + struct i2c_client *client = data->client; + int ret; + + struct i2c_msg msg = { + .addr = client->addr, + .flags = client->flags | I2C_M_RD, + .len = AMS_IAQCORE_DATA_SIZE, + .buf = (char *) &data->buffer, + }; + + ret = i2c_transfer(client->adapter, &msg, 1); + + return (ret == AMS_IAQCORE_DATA_SIZE) ? 0 : ret; +} + +static int ams_iaqcore_get_measurement(struct ams_iaqcore_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = ams_iaqcore_read_measurement(data); + if (ret < 0) + return ret; + + data->last_update = jiffies; + + return 0; +} + +static int ams_iaqcore_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ams_iaqcore_data *data = iio_priv(indio_dev); + int ret; + + if (mask != IIO_CHAN_INFO_PROCESSED) + return -EINVAL; + + mutex_lock(&data->lock); + ret = ams_iaqcore_get_measurement(data); + + if (ret) + goto err_out; + + switch (chan->address) { + case AMS_IAQCORE_VOC_CO2_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.co2_ppm); + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case AMS_IAQCORE_VOC_RESISTANCE_IDX: + *val = be32_to_cpu(data->buffer.resistance); + ret = IIO_VAL_INT; + break; + case AMS_IAQCORE_VOC_TVOC_IDX: + *val = 0; + *val2 = be16_to_cpu(data->buffer.voc_ppb); + ret = IIO_VAL_INT_PLUS_NANO; + break; + default: + ret = -EINVAL; + } + +err_out: + mutex_unlock(&data->lock); + + return ret; +} + +static const struct iio_info ams_iaqcore_info = { + .read_raw = ams_iaqcore_read_raw, + .driver_module = THIS_MODULE, +}; + +static int ams_iaqcore_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct ams_iaqcore_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client = client; + + /* so initial reading will complete */ + data->last_update = jiffies - HZ; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &ams_iaqcore_info, + indio_dev->name = dev_name(&client->dev); + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = ams_iaqcore_channels; + indio_dev->num_channels = ARRAY_SIZE(ams_iaqcore_channels); + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id ams_iaqcore_id[] = { + { "ams-iaq-core", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ams_iaqcore_id); + +static const struct of_device_id ams_iaqcore_dt_ids[] = { + { .compatible = "ams,iaq-core" }, + { } +}; +MODULE_DEVICE_TABLE(of, ams_iaqcore_dt_ids); + +static struct i2c_driver ams_iaqcore_driver = { + .driver = { + .name = "ams-iaq-core", + .of_match_table = of_match_ptr(ams_iaqcore_dt_ids), + }, + .probe = ams_iaqcore_probe, + .id_table = ams_iaqcore_id, +}; +module_i2c_driver(ams_iaqcore_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("AMS iAQ-Core VOC sensors"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/chemical/atlas-ph-sensor.c b/drivers/iio/chemical/atlas-ph-sensor.c new file mode 100644 index 0000000..62b37cd --- /dev/null +++ b/drivers/iio/chemical/atlas-ph-sensor.c @@ -0,0 +1,509 @@ +/* + * atlas-ph-sensor.c - Support for Atlas Scientific OEM pH-SM sensor + * + * Copyright (C) 2015 Matt Ranostay + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ATLAS_REGMAP_NAME "atlas_ph_regmap" +#define ATLAS_DRV_NAME "atlas_ph" + +#define ATLAS_REG_DEV_TYPE 0x00 +#define ATLAS_REG_DEV_VERSION 0x01 + +#define ATLAS_REG_INT_CONTROL 0x04 +#define ATLAS_REG_INT_CONTROL_EN BIT(3) + +#define ATLAS_REG_PWR_CONTROL 0x06 + +#define ATLAS_REG_CALIB_STATUS 0x0d +#define ATLAS_REG_CALIB_STATUS_MASK 0x07 +#define ATLAS_REG_CALIB_STATUS_LOW BIT(0) +#define ATLAS_REG_CALIB_STATUS_MID BIT(1) +#define ATLAS_REG_CALIB_STATUS_HIGH BIT(2) + +#define ATLAS_REG_TEMP_DATA 0x0e +#define ATLAS_REG_PH_DATA 0x16 + +#define ATLAS_PH_INT_TIME_IN_US 450000 + +struct atlas_data { + struct i2c_client *client; + struct iio_trigger *trig; + struct regmap *regmap; + struct irq_work work; + + __be32 buffer[4]; /* 32-bit pH data + 32-bit pad + 64-bit timestamp */ +}; + +static const struct regmap_range atlas_volatile_ranges[] = { + regmap_reg_range(ATLAS_REG_INT_CONTROL, ATLAS_REG_INT_CONTROL), + regmap_reg_range(ATLAS_REG_PH_DATA, ATLAS_REG_PH_DATA + 4), +}; + +static const struct regmap_access_table atlas_volatile_table = { + .yes_ranges = atlas_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(atlas_volatile_ranges), +}; + +static const struct regmap_config atlas_regmap_config = { + .name = ATLAS_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .volatile_table = &atlas_volatile_table, + .max_register = ATLAS_REG_PH_DATA + 4, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct iio_chan_spec atlas_channels[] = { + { + .type = IIO_PH, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(1), + { + .type = IIO_TEMP, + .address = ATLAS_REG_TEMP_DATA, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .output = 1, + .scan_index = -1 + }, +}; + +static int atlas_set_powermode(struct atlas_data *data, int on) +{ + return regmap_write(data->regmap, ATLAS_REG_PWR_CONTROL, on); +} + +static int atlas_set_interrupt(struct atlas_data *data, bool state) +{ + return regmap_update_bits(data->regmap, ATLAS_REG_INT_CONTROL, + ATLAS_REG_INT_CONTROL_EN, + state ? ATLAS_REG_INT_CONTROL_EN : 0); +} + +static int atlas_buffer_postenable(struct iio_dev *indio_dev) +{ + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + ret = iio_triggered_buffer_postenable(indio_dev); + if (ret) + return ret; + + ret = pm_runtime_get_sync(&data->client->dev); + if (ret < 0) { + pm_runtime_put_noidle(&data->client->dev); + return ret; + } + + return atlas_set_interrupt(data, true); +} + +static int atlas_buffer_predisable(struct iio_dev *indio_dev) +{ + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + ret = iio_triggered_buffer_predisable(indio_dev); + if (ret) + return ret; + + ret = atlas_set_interrupt(data, false); + if (ret) + return ret; + + pm_runtime_mark_last_busy(&data->client->dev); + return pm_runtime_put_autosuspend(&data->client->dev); +} + +static const struct iio_trigger_ops atlas_interrupt_trigger_ops = { + .owner = THIS_MODULE, +}; + +static const struct iio_buffer_setup_ops atlas_buffer_setup_ops = { + .postenable = atlas_buffer_postenable, + .predisable = atlas_buffer_predisable, +}; + +static void atlas_work_handler(struct irq_work *work) +{ + struct atlas_data *data = container_of(work, struct atlas_data, work); + + iio_trigger_poll(data->trig); +} + +static irqreturn_t atlas_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct atlas_data *data = iio_priv(indio_dev); + int ret; + + ret = regmap_bulk_read(data->regmap, ATLAS_REG_PH_DATA, + (u8 *) &data->buffer, sizeof(data->buffer[0])); + + if (!ret) + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t atlas_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct atlas_data *data = iio_priv(indio_dev); + + irq_work_queue(&data->work); + + return IRQ_HANDLED; +} + +static int atlas_read_ph_measurement(struct atlas_data *data, __be32 *val) +{ + struct device *dev = &data->client->dev; + int suspended = pm_runtime_suspended(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + return ret; + } + + if (suspended) + usleep_range(ATLAS_PH_INT_TIME_IN_US, + ATLAS_PH_INT_TIME_IN_US + 100000); + + ret = regmap_bulk_read(data->regmap, ATLAS_REG_PH_DATA, + (u8 *) val, sizeof(*val)); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static int atlas_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct atlas_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret; + __be32 reg; + + switch (chan->type) { + case IIO_TEMP: + ret = regmap_bulk_read(data->regmap, chan->address, + (u8 *) ®, sizeof(reg)); + break; + case IIO_PH: + mutex_lock(&indio_dev->mlock); + + if (iio_buffer_enabled(indio_dev)) + ret = -EBUSY; + else + ret = atlas_read_ph_measurement(data, ®); + + mutex_unlock(&indio_dev->mlock); + break; + default: + ret = -EINVAL; + } + + if (!ret) { + *val = be32_to_cpu(reg); + ret = IIO_VAL_INT; + } + return ret; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 1; /* 0.01 */ + *val2 = 100; + break; + case IIO_PH: + *val = 1; /* 0.001 */ + *val2 = 1000; + break; + default: + return -EINVAL; + } + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int atlas_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct atlas_data *data = iio_priv(indio_dev); + __be32 reg = cpu_to_be32(val); + + if (val2 != 0 || val < 0 || val > 20000) + return -EINVAL; + + if (mask != IIO_CHAN_INFO_RAW || chan->type != IIO_TEMP) + return -EINVAL; + + return regmap_bulk_write(data->regmap, chan->address, + ®, sizeof(reg)); +} + +static const struct iio_info atlas_info = { + .driver_module = THIS_MODULE, + .read_raw = atlas_read_raw, + .write_raw = atlas_write_raw, +}; + +static int atlas_check_calibration(struct atlas_data *data) +{ + struct device *dev = &data->client->dev; + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, ATLAS_REG_CALIB_STATUS, &val); + if (ret) + return ret; + + if (!(val & ATLAS_REG_CALIB_STATUS_MASK)) { + dev_warn(dev, "device has not been calibrated\n"); + return 0; + } + + if (!(val & ATLAS_REG_CALIB_STATUS_LOW)) + dev_warn(dev, "device missing low point calibration\n"); + + if (!(val & ATLAS_REG_CALIB_STATUS_MID)) + dev_warn(dev, "device missing mid point calibration\n"); + + if (!(val & ATLAS_REG_CALIB_STATUS_HIGH)) + dev_warn(dev, "device missing high point calibration\n"); + + return 0; +}; + +static int atlas_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct atlas_data *data; + struct iio_trigger *trig; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + indio_dev->info = &atlas_info; + indio_dev->name = ATLAS_DRV_NAME; + indio_dev->channels = atlas_channels; + indio_dev->num_channels = ARRAY_SIZE(atlas_channels); + indio_dev->modes = INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE; + indio_dev->dev.parent = &client->dev; + + trig = devm_iio_trigger_alloc(&client->dev, "%s-dev%d", + indio_dev->name, indio_dev->id); + + if (!trig) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + data->trig = trig; + trig->dev.parent = indio_dev->dev.parent; + trig->ops = &atlas_interrupt_trigger_ops; + iio_trigger_set_drvdata(trig, indio_dev); + + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &atlas_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + ret = pm_runtime_set_active(&client->dev); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + + ret = atlas_check_calibration(data); + if (ret) + return ret; + + ret = iio_trigger_register(trig); + if (ret) { + dev_err(&client->dev, "failed to register trigger\n"); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + &atlas_trigger_handler, &atlas_buffer_setup_ops); + if (ret) { + dev_err(&client->dev, "cannot setup iio trigger\n"); + goto unregister_trigger; + } + + init_irq_work(&data->work, atlas_work_handler); + + /* interrupt pin toggles on new conversion */ + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, atlas_interrupt_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "atlas_irq", + indio_dev); + if (ret) { + dev_err(&client->dev, "request irq (%d) failed\n", client->irq); + goto unregister_buffer; + } + + ret = atlas_set_powermode(data, 1); + if (ret) { + dev_err(&client->dev, "cannot power device on"); + goto unregister_buffer; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, 2500); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&client->dev, "unable to register device\n"); + goto unregister_pm; + } + + return 0; + +unregister_pm: + pm_runtime_disable(&client->dev); + atlas_set_powermode(data, 0); + +unregister_buffer: + iio_triggered_buffer_cleanup(indio_dev); + +unregister_trigger: + iio_trigger_unregister(data->trig); + + return ret; +} + +static int atlas_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct atlas_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + iio_trigger_unregister(data->trig); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + pm_runtime_put_noidle(&client->dev); + + return atlas_set_powermode(data, 0); +} + +#ifdef CONFIG_PM +static int atlas_runtime_suspend(struct device *dev) +{ + struct atlas_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return atlas_set_powermode(data, 0); +} + +static int atlas_runtime_resume(struct device *dev) +{ + struct atlas_data *data = + iio_priv(i2c_get_clientdata(to_i2c_client(dev))); + + return atlas_set_powermode(data, 1); +} +#endif + +static const struct dev_pm_ops atlas_pm_ops = { + SET_RUNTIME_PM_OPS(atlas_runtime_suspend, + atlas_runtime_resume, NULL) +}; + +static const struct i2c_device_id atlas_id[] = { + { "atlas-ph-sm", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, atlas_id); + +static const struct of_device_id atlas_dt_ids[] = { + { .compatible = "atlas,ph-sm" }, + { } +}; +MODULE_DEVICE_TABLE(of, atlas_dt_ids); + +static struct i2c_driver atlas_driver = { + .driver = { + .name = ATLAS_DRV_NAME, + .of_match_table = of_match_ptr(atlas_dt_ids), + .pm = &atlas_pm_ops, + }, + .probe = atlas_probe, + .remove = atlas_remove, + .id_table = atlas_id, +}; +module_i2c_driver(atlas_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("Atlas Scientific pH-SM sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/chemical/vz89x.c b/drivers/iio/chemical/vz89x.c index 11e59a5..652649d 100644 --- a/drivers/iio/chemical/vz89x.c +++ b/drivers/iio/chemical/vz89x.c @@ -34,8 +34,9 @@ struct vz89x_data { struct i2c_client *client; struct mutex lock; - unsigned long last_update; + int (*xfer)(struct vz89x_data *data, u8 cmd); + unsigned long last_update; u8 buffer[VZ89X_REG_MEASUREMENT_SIZE]; }; @@ -100,27 +101,60 @@ static int vz89x_measurement_is_valid(struct vz89x_data *data) return !!(data->buffer[VZ89X_REG_MEASUREMENT_SIZE - 1] > 0); } -static int vz89x_get_measurement(struct vz89x_data *data) +static int vz89x_i2c_xfer(struct vz89x_data *data, u8 cmd) { + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; int ret; - int i; + u8 buf[3] = { cmd, 0, 0}; - /* sensor can only be polled once a second max per datasheet */ - if (!time_after(jiffies, data->last_update + HZ)) - return 0; + msg[0].addr = client->addr; + msg[0].flags = client->flags; + msg[0].len = 3; + msg[0].buf = (char *) &buf; + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = VZ89X_REG_MEASUREMENT_SIZE; + msg[1].buf = (char *) &data->buffer; + + ret = i2c_transfer(client->adapter, msg, 2); - ret = i2c_smbus_write_word_data(data->client, - VZ89X_REG_MEASUREMENT, 0); + return (ret == 2) ? 0 : ret; +} + +static int vz89x_smbus_xfer(struct vz89x_data *data, u8 cmd) +{ + struct i2c_client *client = data->client; + int ret; + int i; + + ret = i2c_smbus_write_word_data(client, cmd, 0); if (ret < 0) return ret; for (i = 0; i < VZ89X_REG_MEASUREMENT_SIZE; i++) { - ret = i2c_smbus_read_byte(data->client); + ret = i2c_smbus_read_byte(client); if (ret < 0) return ret; data->buffer[i] = ret; } + return 0; +} + +static int vz89x_get_measurement(struct vz89x_data *data) +{ + int ret; + + /* sensor can only be polled once a second max per datasheet */ + if (!time_after(jiffies, data->last_update + HZ)) + return 0; + + ret = data->xfer(data, VZ89X_REG_MEASUREMENT); + if (ret < 0) + return ret; + ret = vz89x_measurement_is_valid(data); if (ret) return -EAGAIN; @@ -204,15 +238,19 @@ static int vz89x_probe(struct i2c_client *client, struct iio_dev *indio_dev; struct vz89x_data *data; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | - I2C_FUNC_SMBUS_BYTE)) - return -ENODEV; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; - data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + data->xfer = vz89x_i2c_xfer; + else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = vz89x_smbus_xfer; + else + return -EOPNOTSUPP; + i2c_set_clientdata(client, indio_dev); data->client = client; data->last_update = jiffies - HZ; diff --git a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c index 5955110..5b41f9d 100644 --- a/drivers/iio/common/hid-sensors/hid-sensor-trigger.c +++ b/drivers/iio/common/hid-sensors/hid-sensor-trigger.c @@ -115,7 +115,7 @@ int hid_sensor_power_state(struct hid_sensor_common *st, bool state) return ret; } - return 0; + return 0; #else atomic_set(&st->user_requested_state, state); return _hid_sensor_power_state(st, state); diff --git a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c index 669dc7c..ecf7721 100644 --- a/drivers/iio/common/ms_sensors/ms_sensors_i2c.c +++ b/drivers/iio/common/ms_sensors/ms_sensors_i2c.c @@ -106,7 +106,7 @@ int ms_sensors_convert_and_read(void *cli, u8 conv, u8 rd, unsigned int delay, u32 *adc) { int ret; - __be32 buf = 0; + __be32 buf = 0; struct i2c_client *client = (struct i2c_client *)cli; /* Trigger conversion */ diff --git a/drivers/iio/common/st_sensors/st_sensors_buffer.c b/drivers/iio/common/st_sensors/st_sensors_buffer.c index e18bc67..f1693db 100644 --- a/drivers/iio/common/st_sensors/st_sensors_buffer.c +++ b/drivers/iio/common/st_sensors/st_sensors_buffer.c @@ -24,81 +24,30 @@ int st_sensors_get_buffer_element(struct iio_dev *indio_dev, u8 *buf) { - u8 *addr; - int i, n = 0, len; + int i, len; + int total = 0; struct st_sensor_data *sdata = iio_priv(indio_dev); unsigned int num_data_channels = sdata->num_data_channels; - unsigned int byte_for_channel = - indio_dev->channels[0].scan_type.storagebits >> 3; - - addr = kmalloc(num_data_channels, GFP_KERNEL); - if (!addr) { - len = -ENOMEM; - goto st_sensors_get_buffer_element_error; - } for (i = 0; i < num_data_channels; i++) { + unsigned int bytes_to_read; + if (test_bit(i, indio_dev->active_scan_mask)) { - addr[n] = indio_dev->channels[i].address; - n++; - } - } - switch (n) { - case 1: - len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, - addr[0], byte_for_channel, buf, sdata->multiread_bit); - break; - case 2: - if ((addr[1] - addr[0]) == byte_for_channel) { + bytes_to_read = indio_dev->channels[i].scan_type.storagebits >> 3; len = sdata->tf->read_multiple_byte(&sdata->tb, - sdata->dev, addr[0], byte_for_channel * n, - buf, sdata->multiread_bit); - } else { - u8 *rx_array; - rx_array = kmalloc(byte_for_channel * num_data_channels, - GFP_KERNEL); - if (!rx_array) { - len = -ENOMEM; - goto st_sensors_free_memory; - } + sdata->dev, indio_dev->channels[i].address, + bytes_to_read, + buf + total, sdata->multiread_bit); - len = sdata->tf->read_multiple_byte(&sdata->tb, - sdata->dev, addr[0], - byte_for_channel * num_data_channels, - rx_array, sdata->multiread_bit); - if (len < 0) { - kfree(rx_array); - goto st_sensors_free_memory; - } - - for (i = 0; i < n * byte_for_channel; i++) { - if (i < n) - buf[i] = rx_array[i]; - else - buf[i] = rx_array[n + i]; - } - kfree(rx_array); - len = byte_for_channel * n; + if (len < bytes_to_read) + return -EIO; + + /* Advance the buffer pointer */ + total += len; } - break; - case 3: - len = sdata->tf->read_multiple_byte(&sdata->tb, sdata->dev, - addr[0], byte_for_channel * num_data_channels, - buf, sdata->multiread_bit); - break; - default: - len = -EINVAL; - goto st_sensors_free_memory; - } - if (len != byte_for_channel * n) { - len = -EIO; - goto st_sensors_free_memory; } -st_sensors_free_memory: - kfree(addr); -st_sensors_get_buffer_element_error: - return len; + return total; } EXPORT_SYMBOL(st_sensors_get_buffer_element); @@ -108,13 +57,20 @@ irqreturn_t st_sensors_trigger_handler(int irq, void *p) struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct st_sensor_data *sdata = iio_priv(indio_dev); + s64 timestamp; + + /* If we do timetamping here, do it before reading the values */ + if (sdata->hw_irq_trigger) + timestamp = sdata->hw_timestamp; + else + timestamp = iio_get_time_ns(); len = st_sensors_get_buffer_element(indio_dev, sdata->buffer_data); if (len < 0) goto st_sensors_get_buffer_element_error; iio_push_to_buffers_with_timestamp(indio_dev, sdata->buffer_data, - pf->timestamp); + timestamp); st_sensors_get_buffer_element_error: iio_trigger_notify_done(indio_dev->trig); diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c index 25258e2..9e59c90 100644 --- a/drivers/iio/common/st_sensors/st_sensors_core.c +++ b/drivers/iio/common/st_sensors/st_sensors_core.c @@ -18,16 +18,15 @@ #include #include - -#define ST_SENSORS_WAI_ADDRESS 0x0f +#include "st_sensors_core.h" static inline u32 st_sensors_get_unaligned_le24(const u8 *p) { return (s32)((p[0] | p[1] << 8 | p[2] << 16) << 8) >> 8; } -static int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, - u8 reg_addr, u8 mask, u8 data) +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data) { int err; u8 new_data; @@ -302,6 +301,14 @@ static int st_sensors_set_drdy_int_pin(struct iio_dev *indio_dev, return -EINVAL; } + if (pdata->open_drain) { + if (!sdata->sensor_settings->drdy_irq.addr_od) + dev_err(&indio_dev->dev, + "open drain requested but unsupported.\n"); + else + sdata->int_pin_open_drain = true; + } + return 0; } @@ -322,6 +329,8 @@ static struct st_sensors_platform_data *st_sensors_of_probe(struct device *dev, else pdata->drdy_int_pin = defdata ? defdata->drdy_int_pin : 0; + pdata->open_drain = of_property_read_bool(np, "drive-open-drain"); + return pdata; } #else @@ -354,6 +363,11 @@ int st_sensors_init_sensor(struct iio_dev *indio_dev, if (err < 0) return err; + /* Disable DRDY, this might be still be enabled after reboot. */ + err = st_sensors_set_dataready_irq(indio_dev, false); + if (err < 0) + return err; + if (sdata->current_fullscale) { err = st_sensors_set_fullscale(indio_dev, sdata->current_fullscale->num); @@ -375,6 +389,16 @@ int st_sensors_init_sensor(struct iio_dev *indio_dev, return err; } + if (sdata->int_pin_open_drain) { + dev_info(&indio_dev->dev, + "set interrupt line to open drain mode\n"); + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.addr_od, + sdata->sensor_settings->drdy_irq.mask_od, 1); + if (err < 0) + return err; + } + err = st_sensors_set_axis_enable(indio_dev, ST_SENSORS_ENABLE_ALL_AXIS); return err; @@ -405,6 +429,9 @@ int st_sensors_set_dataready_irq(struct iio_dev *indio_dev, bool enable) else drdy_mask = sdata->sensor_settings->drdy_irq.mask_int2; + /* Flag to the poll function that the hardware trigger is in use */ + sdata->hw_irq_trigger = enable; + /* Enable/Disable the interrupt generator for data ready. */ err = st_sensors_write_data_with_mask(indio_dev, sdata->sensor_settings->drdy_irq.addr, diff --git b/drivers/iio/common/st_sensors/st_sensors_core.h b/drivers/iio/common/st_sensors/st_sensors_core.h new file mode 100644 index 0000000..cd88098 --- /dev/null +++ b/drivers/iio/common/st_sensors/st_sensors_core.h @@ -0,0 +1,8 @@ +/* + * Local functions in the ST Sensors core + */ +#ifndef __ST_SENSORS_CORE_H +#define __ST_SENSORS_CORE_H +int st_sensors_write_data_with_mask(struct iio_dev *indio_dev, + u8 reg_addr, u8 mask, u8 data); +#endif diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c index 3e90704..296e4ff 100644 --- a/drivers/iio/common/st_sensors/st_sensors_trigger.c +++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c @@ -14,38 +14,154 @@ #include #include #include - #include +#include "st_sensors_core.h" + +/** + * st_sensors_irq_handler() - top half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +irqreturn_t st_sensors_irq_handler(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + + /* Get the time stamp as close in time as possible */ + sdata->hw_timestamp = iio_get_time_ns(); + return IRQ_WAKE_THREAD; +} + +/** + * st_sensors_irq_thread() - bottom half of the IRQ-based triggers + * @irq: irq number + * @p: private handler data + */ +irqreturn_t st_sensors_irq_thread(int irq, void *p) +{ + struct iio_trigger *trig = p; + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct st_sensor_data *sdata = iio_priv(indio_dev); + int ret; + + /* + * If this trigger is backed by a hardware interrupt and we have a + * status register, check if this IRQ came from us + */ + if (sdata->sensor_settings->drdy_irq.addr_stat_drdy) { + u8 status; + ret = sdata->tf->read_byte(&sdata->tb, sdata->dev, + sdata->sensor_settings->drdy_irq.addr_stat_drdy, + &status); + if (ret < 0) { + dev_err(sdata->dev, "could not read channel status\n"); + goto out_poll; + } + /* + * the lower bits of .active_scan_mask[0] is directly mapped + * to the channels on the sensor: either bit 0 for + * one-dimensional sensors, or e.g. x,y,z for accelerometers, + * gyroscopes or magnetometers. No sensor use more than 3 + * channels, so cut the other status bits here. + */ + status &= 0x07; + + /* + * If this was not caused by any channels on this sensor, + * return IRQ_NONE + */ + if (!indio_dev->active_scan_mask) + return IRQ_NONE; + if (!(status & (u8)indio_dev->active_scan_mask[0])) + return IRQ_NONE; + } + +out_poll: + /* It's our IRQ: proceed to handle the register polling */ + iio_trigger_poll_chained(p); + return IRQ_HANDLED; +} int st_sensors_allocate_trigger(struct iio_dev *indio_dev, const struct iio_trigger_ops *trigger_ops) { - int err; + int err, irq; struct st_sensor_data *sdata = iio_priv(indio_dev); + unsigned long irq_trig; sdata->trig = iio_trigger_alloc("%s-trigger", indio_dev->name); if (sdata->trig == NULL) { - err = -ENOMEM; dev_err(&indio_dev->dev, "failed to allocate iio trigger.\n"); - goto iio_trigger_alloc_error; + return -ENOMEM; } + iio_trigger_set_drvdata(sdata->trig, indio_dev); + sdata->trig->ops = trigger_ops; + sdata->trig->dev.parent = sdata->dev; + + irq = sdata->get_irq_data_ready(indio_dev); + irq_trig = irqd_get_trigger_type(irq_get_irq_data(irq)); + /* + * If the IRQ is triggered on falling edge, we need to mark the + * interrupt as active low, if the hardware supports this. + */ + if (irq_trig == IRQF_TRIGGER_FALLING) { + if (!sdata->sensor_settings->drdy_irq.addr_ihl) { + dev_err(&indio_dev->dev, + "falling edge specified for IRQ but hardware " + "only support rising edge, will request " + "rising edge\n"); + irq_trig = IRQF_TRIGGER_RISING; + } else { + /* Set up INT active low i.e. falling edge */ + err = st_sensors_write_data_with_mask(indio_dev, + sdata->sensor_settings->drdy_irq.addr_ihl, + sdata->sensor_settings->drdy_irq.mask_ihl, 1); + if (err < 0) + goto iio_trigger_free; + dev_info(&indio_dev->dev, + "interrupts on the falling edge\n"); + } + } else if (irq_trig == IRQF_TRIGGER_RISING) { + dev_info(&indio_dev->dev, + "interrupts on the rising edge\n"); + + } else { + dev_err(&indio_dev->dev, + "unsupported IRQ trigger specified (%lx), only " + "rising and falling edges supported, enforce " + "rising edge\n", irq_trig); + irq_trig = IRQF_TRIGGER_RISING; + } + + /* + * If the interrupt pin is Open Drain, by definition this + * means that the interrupt line may be shared with other + * peripherals. But to do this we also need to have a status + * register and mask to figure out if this sensor was firing + * the IRQ or not, so we can tell the interrupt handle that + * it was "our" interrupt. + */ + if (sdata->int_pin_open_drain && + sdata->sensor_settings->drdy_irq.addr_stat_drdy) + irq_trig |= IRQF_SHARED; + + /* Let's create an interrupt thread masking the hard IRQ here */ + irq_trig |= IRQF_ONESHOT; + err = request_threaded_irq(sdata->get_irq_data_ready(indio_dev), - iio_trigger_generic_data_rdy_poll, - NULL, - IRQF_TRIGGER_RISING, + st_sensors_irq_handler, + st_sensors_irq_thread, + irq_trig, sdata->trig->name, sdata->trig); if (err) { dev_err(&indio_dev->dev, "failed to request trigger IRQ.\n"); - goto request_irq_error; + goto iio_trigger_free; } - iio_trigger_set_drvdata(sdata->trig, indio_dev); - sdata->trig->ops = trigger_ops; - sdata->trig->dev.parent = sdata->dev; - err = iio_trigger_register(sdata->trig); if (err < 0) { dev_err(&indio_dev->dev, "failed to register iio trigger.\n"); @@ -57,9 +173,8 @@ int st_sensors_allocate_trigger(struct iio_dev *indio_dev, iio_trigger_register_error: free_irq(sdata->get_irq_data_ready(indio_dev), sdata->trig); -request_irq_error: +iio_trigger_free: iio_trigger_free(sdata->trig); -iio_trigger_alloc_error: return err; } EXPORT_SYMBOL(st_sensors_allocate_trigger); @@ -74,6 +189,18 @@ void st_sensors_deallocate_trigger(struct iio_dev *indio_dev) } EXPORT_SYMBOL(st_sensors_deallocate_trigger); +int st_sensors_validate_device(struct iio_trigger *trig, + struct iio_dev *indio_dev) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + + if (indio != indio_dev) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL(st_sensors_validate_device); + MODULE_AUTHOR("Denis Ciocca "); MODULE_DESCRIPTION("STMicroelectronics ST-sensors trigger"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index e701e28..f7c71da 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -10,8 +10,10 @@ config AD5064 depends on (SPI_MASTER && I2C!=m) || I2C help Say yes here to build support for Analog Devices AD5024, AD5025, AD5044, - AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5629R, AD5648, AD5666, AD5668, - AD5669R Digital to Analog Converter. + AD5045, AD5064, AD5064-1, AD5065, AD5625, AD5625R, AD5627, AD5627R, + AD5628, AD5629R, AD5645R, AD5647R, AD5648, AD5665, AD5665R, AD5666, + AD5667, AD5667R, AD5668, AD5669R, LTC2606, LTC2607, LTC2609, LTC2616, + LTC2617, LTC2619, LTC2626, LTC2627, LTC2629 Digital to Analog Converter. To compile this driver as a module, choose M here: the module will be called ad5064. @@ -72,6 +74,33 @@ config AD5449 To compile this driver as a module, choose M here: the module will be called ad5449. +config AD5592R_BASE + tristate + +config AD5592R + tristate "Analog Devices AD5592R ADC/DAC driver" + depends on SPI_MASTER + select GPIOLIB + select AD5592R_BASE + help + Say yes here to build support for Analog Devices AD5592R + Digital to Analog / Analog to Digital Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5592r. + +config AD5593R + tristate "Analog Devices AD5593R ADC/DAC driver" + depends on I2C + select GPIOLIB + select AD5592R_BASE + help + Say yes here to build support for Analog Devices AD5593R + Digital to Analog / Analog to Digital Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5593r. + config AD5504 tristate "Analog Devices AD5504/AD5501 DAC SPI driver" depends on SPI @@ -111,6 +140,16 @@ config AD5755 To compile this driver as a module, choose M here: the module will be called ad5755. +config AD5761 + tristate "Analog Devices AD5761/61R/21/21R DAC driver" + depends on SPI_MASTER + help + Say yes here to build support for Analog Devices AD5761, AD5761R, AD5721, + AD5721R Digital to Analog Converter. + + To compile this driver as a module, choose M here: the + module will be called ad5761. + config AD5764 tristate "Analog Devices AD5764/64R/44/44R DAC driver" depends on SPI_MASTER @@ -142,6 +181,16 @@ config AD7303 To compile this driver as module choose M here: the module will be called ad7303. +config LPC18XX_DAC + tristate "NXP LPC18xx DAC driver" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on OF && HAS_IOMEM + help + Say yes here to build support for NXP LPC18XX DAC. + + To compile this driver as a module, choose M here: the module will be + called lpc18xx_dac. + config M62332 tristate "Mitsubishi M62332 DAC driver" depends on I2C @@ -176,11 +225,11 @@ config MAX5821 10 bits DAC. config MCP4725 - tristate "MCP4725 DAC driver" + tristate "MCP4725/6 DAC driver" depends on I2C ---help--- Say Y here if you want to build a driver for the Microchip - MCP 4725 12-bit digital-to-analog converter (DAC) with I2C + MCP 4725/6 12-bit digital-to-analog converter (DAC) with I2C interface. To compile this driver as a module, choose M here: the module @@ -196,4 +245,23 @@ config MCP4922 To compile this driver as a module, choose M here: the module will be called mcp4922. +config STX104 + tristate "Apex Embedded Systems STX104 DAC driver" + depends on X86 && ISA_BUS_API + help + Say yes here to build support for the 2-channel DAC on the Apex + Embedded Systems STX104 integrated analog PC/104 card. The base port + addresses for the devices may be configured via the "base" module + parameter array. + +config VF610_DAC + tristate "Vybrid vf610 DAC driver" + depends on OF + depends on HAS_IOMEM + help + Say yes here to support Vybrid board digital-to-analog converter. + + This driver can also be built as a module. If so, the module will + be called vf610_dac. + endmenu diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 63ae056..8b78d5c 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -11,13 +11,20 @@ obj-$(CONFIG_AD5064) += ad5064.o obj-$(CONFIG_AD5504) += ad5504.o obj-$(CONFIG_AD5446) += ad5446.o obj-$(CONFIG_AD5449) += ad5449.o +obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o +obj-$(CONFIG_AD5592R) += ad5592r.o +obj-$(CONFIG_AD5593R) += ad5593r.o obj-$(CONFIG_AD5755) += ad5755.o +obj-$(CONFIG_AD5761) += ad5761.o obj-$(CONFIG_AD5764) += ad5764.o obj-$(CONFIG_AD5791) += ad5791.o obj-$(CONFIG_AD5686) += ad5686.o obj-$(CONFIG_AD7303) += ad7303.o +obj-$(CONFIG_LPC18XX_DAC) += lpc18xx_dac.o obj-$(CONFIG_M62332) += m62332.o obj-$(CONFIG_MAX517) += max517.o obj-$(CONFIG_MAX5821) += max5821.o obj-$(CONFIG_MCP4725) += mcp4725.o obj-$(CONFIG_MCP4922) += mcp4922.o +obj-$(CONFIG_STX104) += stx104.o +obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ad5064.c b/drivers/iio/dac/ad5064.c index 81ca008..6803e4a 100644 --- a/drivers/iio/dac/ad5064.c +++ b/drivers/iio/dac/ad5064.c @@ -1,6 +1,9 @@ /* - * AD5024, AD5025, AD5044, AD5045, AD5064, AD5064-1, AD5065, AD5628, AD5629R, - * AD5648, AD5666, AD5668, AD5669R Digital to analog converters driver + * AD5024, AD5025, AD5044, AD5045, AD5064, AD5064-1, AD5065, AD5625, AD5625R, + * AD5627, AD5627R, AD5628, AD5629R, AD5645R, AD5647R, AD5648, AD5665, AD5665R, + * AD5666, AD5667, AD5667R, AD5668, AD5669R, LTC2606, LTC2607, LTC2609, LTC2616, + * LTC2617, LTC2619, LTC2626, LTC2627, LTC2629 Digital to analog converters + * driver * * Copyright 2011 Analog Devices Inc. * @@ -39,6 +42,9 @@ #define AD5064_CMD_RESET 0x7 #define AD5064_CMD_CONFIG 0x8 +#define AD5064_CMD_RESET_V2 0x5 +#define AD5064_CMD_CONFIG_V2 0x7 + #define AD5064_CONFIG_DAISY_CHAIN_ENABLE BIT(1) #define AD5064_CONFIG_INT_VREF_ENABLE BIT(0) @@ -48,12 +54,25 @@ #define AD5064_LDAC_PWRDN_3STATE 0x3 /** + * enum ad5064_regmap_type - Register layout variant + * @AD5064_REGMAP_ADI: Old Analog Devices register map layout + * @AD5064_REGMAP_ADI2: New Analog Devices register map layout + * @AD5064_REGMAP_LTC: LTC register map layout + */ +enum ad5064_regmap_type { + AD5064_REGMAP_ADI, + AD5064_REGMAP_ADI2, + AD5064_REGMAP_LTC, +}; + +/** * struct ad5064_chip_info - chip specific information * @shared_vref: whether the vref supply is shared between channels - * @internal_vref: internal reference voltage. 0 if the chip has no internal - * vref. + * @internal_vref: internal reference voltage. 0 if the chip has no + internal vref. * @channel: channel specification * @num_channels: number of channels + * @regmap_type: register map layout variant */ struct ad5064_chip_info { @@ -61,6 +80,7 @@ struct ad5064_chip_info { unsigned long internal_vref; const struct iio_chan_spec *channels; unsigned int num_channels; + enum ad5064_regmap_type regmap_type; }; struct ad5064_state; @@ -111,18 +131,43 @@ enum ad5064_type { ID_AD5064, ID_AD5064_1, ID_AD5065, + ID_AD5625, + ID_AD5625R_1V25, + ID_AD5625R_2V5, + ID_AD5627, + ID_AD5627R_1V25, + ID_AD5627R_2V5, ID_AD5628_1, ID_AD5628_2, ID_AD5629_1, ID_AD5629_2, + ID_AD5645R_1V25, + ID_AD5645R_2V5, + ID_AD5647R_1V25, + ID_AD5647R_2V5, ID_AD5648_1, ID_AD5648_2, + ID_AD5665, + ID_AD5665R_1V25, + ID_AD5665R_2V5, ID_AD5666_1, ID_AD5666_2, + ID_AD5667, + ID_AD5667R_1V25, + ID_AD5667R_2V5, ID_AD5668_1, ID_AD5668_2, ID_AD5669_1, ID_AD5669_2, + ID_LTC2606, + ID_LTC2607, + ID_LTC2609, + ID_LTC2616, + ID_LTC2617, + ID_LTC2619, + ID_LTC2626, + ID_LTC2627, + ID_LTC2629, }; static int ad5064_write(struct ad5064_state *st, unsigned int cmd, @@ -136,15 +181,27 @@ static int ad5064_write(struct ad5064_state *st, unsigned int cmd, static int ad5064_sync_powerdown_mode(struct ad5064_state *st, const struct iio_chan_spec *chan) { - unsigned int val; + unsigned int val, address; + unsigned int shift; int ret; - val = (0x1 << chan->address); + if (st->chip_info->regmap_type == AD5064_REGMAP_LTC) { + val = 0; + address = chan->address; + } else { + if (st->chip_info->regmap_type == AD5064_REGMAP_ADI2) + shift = 4; + else + shift = 8; + + val = (0x1 << chan->address); + address = 0; - if (st->pwr_down[chan->channel]) - val |= st->pwr_down_mode[chan->channel] << 8; + if (st->pwr_down[chan->channel]) + val |= st->pwr_down_mode[chan->channel] << shift; + } - ret = ad5064_write(st, AD5064_CMD_POWERDOWN_DAC, 0, val, 0); + ret = ad5064_write(st, AD5064_CMD_POWERDOWN_DAC, address, val, 0); return ret; } @@ -155,6 +212,10 @@ static const char * const ad5064_powerdown_modes[] = { "three_state", }; +static const char * const ltc2617_powerdown_modes[] = { + "90kohm_to_gnd", +}; + static int ad5064_get_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *chan) { @@ -185,6 +246,13 @@ static const struct iio_enum ad5064_powerdown_mode_enum = { .set = ad5064_set_powerdown_mode, }; +static const struct iio_enum ltc2617_powerdown_mode_enum = { + .items = ltc2617_powerdown_modes, + .num_items = ARRAY_SIZE(ltc2617_powerdown_modes), + .get = ad5064_get_powerdown_mode, + .set = ad5064_set_powerdown_mode, +}; + static ssize_t ad5064_read_dac_powerdown(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, char *buf) { @@ -295,7 +363,19 @@ static const struct iio_chan_spec_ext_info ad5064_ext_info[] = { { }, }; -#define AD5064_CHANNEL(chan, addr, bits, _shift) { \ +static const struct iio_chan_spec_ext_info ltc2617_ext_info[] = { + { + .name = "powerdown", + .read = ad5064_read_dac_powerdown, + .write = ad5064_write_dac_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, <c2617_powerdown_mode_enum), + IIO_ENUM_AVAILABLE("powerdown_mode", <c2617_powerdown_mode_enum), + { }, +}; + +#define AD5064_CHANNEL(chan, addr, bits, _shift, _ext_info) { \ .type = IIO_VOLTAGE, \ .indexed = 1, \ .output = 1, \ @@ -309,145 +389,340 @@ static const struct iio_chan_spec_ext_info ad5064_ext_info[] = { .storagebits = 16, \ .shift = (_shift), \ }, \ - .ext_info = ad5064_ext_info, \ + .ext_info = (_ext_info), \ } -#define DECLARE_AD5064_CHANNELS(name, bits, shift) \ +#define DECLARE_AD5064_CHANNELS(name, bits, shift, ext_info) \ const struct iio_chan_spec name[] = { \ - AD5064_CHANNEL(0, 0, bits, shift), \ - AD5064_CHANNEL(1, 1, bits, shift), \ - AD5064_CHANNEL(2, 2, bits, shift), \ - AD5064_CHANNEL(3, 3, bits, shift), \ - AD5064_CHANNEL(4, 4, bits, shift), \ - AD5064_CHANNEL(5, 5, bits, shift), \ - AD5064_CHANNEL(6, 6, bits, shift), \ - AD5064_CHANNEL(7, 7, bits, shift), \ + AD5064_CHANNEL(0, 0, bits, shift, ext_info), \ + AD5064_CHANNEL(1, 1, bits, shift, ext_info), \ + AD5064_CHANNEL(2, 2, bits, shift, ext_info), \ + AD5064_CHANNEL(3, 3, bits, shift, ext_info), \ + AD5064_CHANNEL(4, 4, bits, shift, ext_info), \ + AD5064_CHANNEL(5, 5, bits, shift, ext_info), \ + AD5064_CHANNEL(6, 6, bits, shift, ext_info), \ + AD5064_CHANNEL(7, 7, bits, shift, ext_info), \ } -#define DECLARE_AD5065_CHANNELS(name, bits, shift) \ +#define DECLARE_AD5065_CHANNELS(name, bits, shift, ext_info) \ const struct iio_chan_spec name[] = { \ - AD5064_CHANNEL(0, 0, bits, shift), \ - AD5064_CHANNEL(1, 3, bits, shift), \ + AD5064_CHANNEL(0, 0, bits, shift, ext_info), \ + AD5064_CHANNEL(1, 3, bits, shift, ext_info), \ } -static DECLARE_AD5064_CHANNELS(ad5024_channels, 12, 8); -static DECLARE_AD5064_CHANNELS(ad5044_channels, 14, 6); -static DECLARE_AD5064_CHANNELS(ad5064_channels, 16, 4); +static DECLARE_AD5064_CHANNELS(ad5024_channels, 12, 8, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5044_channels, 14, 6, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5064_channels, 16, 4, ad5064_ext_info); + +static DECLARE_AD5065_CHANNELS(ad5025_channels, 12, 8, ad5064_ext_info); +static DECLARE_AD5065_CHANNELS(ad5045_channels, 14, 6, ad5064_ext_info); +static DECLARE_AD5065_CHANNELS(ad5065_channels, 16, 4, ad5064_ext_info); -static DECLARE_AD5065_CHANNELS(ad5025_channels, 12, 8); -static DECLARE_AD5065_CHANNELS(ad5045_channels, 14, 6); -static DECLARE_AD5065_CHANNELS(ad5065_channels, 16, 4); +static DECLARE_AD5064_CHANNELS(ad5629_channels, 12, 4, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5645_channels, 14, 2, ad5064_ext_info); +static DECLARE_AD5064_CHANNELS(ad5669_channels, 16, 0, ad5064_ext_info); -static DECLARE_AD5064_CHANNELS(ad5629_channels, 12, 4); -static DECLARE_AD5064_CHANNELS(ad5669_channels, 16, 0); +static DECLARE_AD5064_CHANNELS(ltc2607_channels, 16, 0, ltc2617_ext_info); +static DECLARE_AD5064_CHANNELS(ltc2617_channels, 14, 2, ltc2617_ext_info); +static DECLARE_AD5064_CHANNELS(ltc2627_channels, 12, 4, ltc2617_ext_info); static const struct ad5064_chip_info ad5064_chip_info_tbl[] = { [ID_AD5024] = { .shared_vref = false, .channels = ad5024_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5025] = { .shared_vref = false, .channels = ad5025_channels, .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5044] = { .shared_vref = false, .channels = ad5044_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5045] = { .shared_vref = false, .channels = ad5045_channels, .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5064] = { .shared_vref = false, .channels = ad5064_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5064_1] = { .shared_vref = true, .channels = ad5064_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5065] = { .shared_vref = false, .channels = ad5065_channels, .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5625] = { + .shared_vref = true, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5625R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5625R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5629_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627] = { + .shared_vref = true, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5627R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5629_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 }, [ID_AD5628_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5024_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5628_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5024_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5629_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5629_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5629_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5629_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5645R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5645_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5645R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5645_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5647R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5645_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5647R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5645_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 }, [ID_AD5648_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5044_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5648_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5044_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5665] = { + .shared_vref = true, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5665R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5665R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5669_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI2 }, [ID_AD5666_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5064_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5666_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5064_channels, .num_channels = 4, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_AD5667] = { + .shared_vref = true, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5667R_1V25] = { + .shared_vref = true, + .internal_vref = 1250000, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 + }, + [ID_AD5667R_2V5] = { + .shared_vref = true, + .internal_vref = 2500000, + .channels = ad5669_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_ADI2 }, [ID_AD5668_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5064_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5668_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5064_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5669_1] = { .shared_vref = true, .internal_vref = 2500000, .channels = ad5669_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, }, [ID_AD5669_2] = { .shared_vref = true, .internal_vref = 5000000, .channels = ad5669_channels, .num_channels = 8, + .regmap_type = AD5064_REGMAP_ADI, + }, + [ID_LTC2606] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2607] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2609] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2607_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2616] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2617] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2619] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2617_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2626] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 1, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2627] = { + .shared_vref = true, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 2, + .regmap_type = AD5064_REGMAP_LTC, + }, + [ID_LTC2629] = { + .shared_vref = false, + .internal_vref = 0, + .channels = ltc2627_channels, + .num_channels = 4, + .regmap_type = AD5064_REGMAP_LTC, }, }; @@ -469,6 +744,22 @@ static const char * const ad5064_vref_name(struct ad5064_state *st, return st->chip_info->shared_vref ? "vref" : ad5064_vref_names[vref]; } +static int ad5064_set_config(struct ad5064_state *st, unsigned int val) +{ + unsigned int cmd; + + switch (st->chip_info->regmap_type) { + case AD5064_REGMAP_ADI2: + cmd = AD5064_CMD_CONFIG_V2; + break; + default: + cmd = AD5064_CMD_CONFIG; + break; + } + + return ad5064_write(st, cmd, 0, val, 0); +} + static int ad5064_probe(struct device *dev, enum ad5064_type type, const char *name, ad5064_write_func write) { @@ -498,8 +789,7 @@ static int ad5064_probe(struct device *dev, enum ad5064_type type, if (!st->chip_info->internal_vref) return ret; st->use_internal_vref = true; - ret = ad5064_write(st, AD5064_CMD_CONFIG, 0, - AD5064_CONFIG_INT_VREF_ENABLE, 0); + ret = ad5064_set_config(st, AD5064_CONFIG_INT_VREF_ENABLE); if (ret) { dev_err(dev, "Failed to enable internal vref: %d\n", ret); @@ -628,9 +918,19 @@ static int ad5064_i2c_write(struct ad5064_state *st, unsigned int cmd, unsigned int addr, unsigned int val) { struct i2c_client *i2c = to_i2c_client(st->dev); + unsigned int cmd_shift; int ret; - st->data.i2c[0] = (cmd << 4) | addr; + switch (st->chip_info->regmap_type) { + case AD5064_REGMAP_ADI2: + cmd_shift = 3; + break; + default: + cmd_shift = 4; + break; + } + + st->data.i2c[0] = (cmd << cmd_shift) | addr; put_unaligned_be16(val, &st->data.i2c[1]); ret = i2c_master_send(i2c, st->data.i2c, 3); @@ -653,12 +953,35 @@ static int ad5064_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id ad5064_i2c_ids[] = { + {"ad5625", ID_AD5625 }, + {"ad5625r-1v25", ID_AD5625R_1V25 }, + {"ad5625r-2v5", ID_AD5625R_2V5 }, + {"ad5627", ID_AD5627 }, + {"ad5627r-1v25", ID_AD5627R_1V25 }, + {"ad5627r-2v5", ID_AD5627R_2V5 }, {"ad5629-1", ID_AD5629_1}, {"ad5629-2", ID_AD5629_2}, {"ad5629-3", ID_AD5629_2}, /* similar enough to ad5629-2 */ + {"ad5645r-1v25", ID_AD5645R_1V25 }, + {"ad5645r-2v5", ID_AD5645R_2V5 }, + {"ad5665", ID_AD5665 }, + {"ad5665r-1v25", ID_AD5665R_1V25 }, + {"ad5665r-2v5", ID_AD5665R_2V5 }, + {"ad5667", ID_AD5667 }, + {"ad5667r-1v25", ID_AD5667R_1V25 }, + {"ad5667r-2v5", ID_AD5667R_2V5 }, {"ad5669-1", ID_AD5669_1}, {"ad5669-2", ID_AD5669_2}, {"ad5669-3", ID_AD5669_2}, /* similar enough to ad5669-2 */ + {"ltc2606", ID_LTC2606}, + {"ltc2607", ID_LTC2607}, + {"ltc2609", ID_LTC2609}, + {"ltc2616", ID_LTC2616}, + {"ltc2617", ID_LTC2617}, + {"ltc2619", ID_LTC2619}, + {"ltc2626", ID_LTC2626}, + {"ltc2627", ID_LTC2627}, + {"ltc2629", ID_LTC2629}, {} }; MODULE_DEVICE_TABLE(i2c, ad5064_i2c_ids); diff --git b/drivers/iio/dac/ad5592r-base.c b/drivers/iio/dac/ad5592r-base.c new file mode 100644 index 0000000..69bde59 --- /dev/null +++ b/drivers/iio/dac/ad5592r-base.c @@ -0,0 +1,691 @@ +/* + * AD5592R Digital <-> Analog converters driver + * + * Copyright 2014-2016 Analog Devices Inc. + * Author: Paul Cercueil + * + * Licensed under the GPL-2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ad5592r-base.h" + +static int ad5592r_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret = 0; + u8 val; + + mutex_lock(&st->gpio_lock); + + if (st->gpio_out & BIT(offset)) + val = st->gpio_val; + else + ret = st->ops->gpio_read(st, &val); + + mutex_unlock(&st->gpio_lock); + + if (ret < 0) + return ret; + + return !!(val & BIT(offset)); +} + +static void ad5592r_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + + mutex_lock(&st->gpio_lock); + + if (value) + st->gpio_val |= BIT(offset); + else + st->gpio_val &= ~BIT(offset); + + st->ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); + + mutex_unlock(&st->gpio_lock); +} + +static int ad5592r_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret; + + mutex_lock(&st->gpio_lock); + + st->gpio_out &= ~BIT(offset); + st->gpio_in |= BIT(offset); + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret < 0) + goto err_unlock; + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); + +err_unlock: + mutex_unlock(&st->gpio_lock); + + return ret; +} + +static int ad5592r_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + int ret; + + mutex_lock(&st->gpio_lock); + + if (value) + st->gpio_val |= BIT(offset); + else + st->gpio_val &= ~BIT(offset); + + st->gpio_in &= ~BIT(offset); + st->gpio_out |= BIT(offset); + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); + if (ret < 0) + goto err_unlock; + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret < 0) + goto err_unlock; + + ret = st->ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); + +err_unlock: + mutex_unlock(&st->gpio_lock); + + return ret; +} + +static int ad5592r_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + struct ad5592r_state *st = gpiochip_get_data(chip); + + if (!(st->gpio_map & BIT(offset))) { + dev_err(st->dev, "GPIO %d is reserved by alternate function\n", + offset); + return -ENODEV; + } + + return 0; +} + +static int ad5592r_gpio_init(struct ad5592r_state *st) +{ + if (!st->gpio_map) + return 0; + + st->gpiochip.label = dev_name(st->dev); + st->gpiochip.base = -1; + st->gpiochip.ngpio = 8; + st->gpiochip.parent = st->dev; + st->gpiochip.can_sleep = true; + st->gpiochip.direction_input = ad5592r_gpio_direction_input; + st->gpiochip.direction_output = ad5592r_gpio_direction_output; + st->gpiochip.get = ad5592r_gpio_get; + st->gpiochip.set = ad5592r_gpio_set; + st->gpiochip.request = ad5592r_gpio_request; + st->gpiochip.owner = THIS_MODULE; + + mutex_init(&st->gpio_lock); + + return gpiochip_add_data(&st->gpiochip, st); +} + +static void ad5592r_gpio_cleanup(struct ad5592r_state *st) +{ + if (st->gpio_map) + gpiochip_remove(&st->gpiochip); +} + +static int ad5592r_reset(struct ad5592r_state *st) +{ + struct gpio_desc *gpio; + struct iio_dev *iio_dev = iio_priv_to_dev(st); + + gpio = devm_gpiod_get_optional(st->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + udelay(1); + gpiod_set_value(gpio, 1); + } else { + mutex_lock(&iio_dev->mlock); + /* Writing this magic value resets the device */ + st->ops->reg_write(st, AD5592R_REG_RESET, 0xdac); + mutex_unlock(&iio_dev->mlock); + } + + udelay(250); + + return 0; +} + +static int ad5592r_get_vref(struct ad5592r_state *st) +{ + int ret; + + if (st->reg) { + ret = regulator_get_voltage(st->reg); + if (ret < 0) + return ret; + + return ret / 1000; + } else { + return 2500; + } +} + +static int ad5592r_set_channel_modes(struct ad5592r_state *st) +{ + const struct ad5592r_rw_ops *ops = st->ops; + int ret; + unsigned i; + struct iio_dev *iio_dev = iio_priv_to_dev(st); + u8 pulldown = 0, tristate = 0, dac = 0, adc = 0; + u16 read_back; + + for (i = 0; i < st->num_channels; i++) { + switch (st->channel_modes[i]) { + case CH_MODE_DAC: + dac |= BIT(i); + break; + + case CH_MODE_ADC: + adc |= BIT(i); + break; + + case CH_MODE_DAC_AND_ADC: + dac |= BIT(i); + adc |= BIT(i); + break; + + case CH_MODE_GPIO: + st->gpio_map |= BIT(i); + st->gpio_in |= BIT(i); /* Default to input */ + break; + + case CH_MODE_UNUSED: + /* fall-through */ + default: + switch (st->channel_offstate[i]) { + case CH_OFFSTATE_OUT_TRISTATE: + tristate |= BIT(i); + break; + + case CH_OFFSTATE_OUT_LOW: + st->gpio_out |= BIT(i); + break; + + case CH_OFFSTATE_OUT_HIGH: + st->gpio_out |= BIT(i); + st->gpio_val |= BIT(i); + break; + + case CH_OFFSTATE_PULLDOWN: + /* fall-through */ + default: + pulldown |= BIT(i); + break; + } + } + } + + mutex_lock(&iio_dev->mlock); + + /* Pull down unused pins to GND */ + ret = ops->reg_write(st, AD5592R_REG_PULLDOWN, pulldown); + if (ret) + goto err_unlock; + + ret = ops->reg_write(st, AD5592R_REG_TRISTATE, tristate); + if (ret) + goto err_unlock; + + /* Configure pins that we use */ + ret = ops->reg_write(st, AD5592R_REG_DAC_EN, dac); + if (ret) + goto err_unlock; + + ret = ops->reg_write(st, AD5592R_REG_ADC_EN, adc); + if (ret) + goto err_unlock; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_SET, st->gpio_val); + if (ret) + goto err_unlock; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_OUT_EN, st->gpio_out); + if (ret) + goto err_unlock; + + ret = ops->reg_write(st, AD5592R_REG_GPIO_IN_EN, st->gpio_in); + if (ret) + goto err_unlock; + + /* Verify that we can read back at least one register */ + ret = ops->reg_read(st, AD5592R_REG_ADC_EN, &read_back); + if (!ret && (read_back & 0xff) != adc) + ret = -EIO; + +err_unlock: + mutex_unlock(&iio_dev->mlock); + return ret; +} + +static int ad5592r_reset_channel_modes(struct ad5592r_state *st) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(st->channel_modes); i++) + st->channel_modes[i] = CH_MODE_UNUSED; + + return ad5592r_set_channel_modes(st); +} + +static int ad5592r_write_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + if (!chan->output) + return -EINVAL; + + mutex_lock(&iio_dev->mlock); + ret = st->ops->write_dac(st, chan->channel, val); + if (!ret) + st->cached_dac[chan->channel] = val; + mutex_unlock(&iio_dev->mlock); + return ret; + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_VOLTAGE) { + bool gain; + + if (val == st->scale_avail[0][0] && + val2 == st->scale_avail[0][1]) + gain = false; + else if (val == st->scale_avail[1][0] && + val2 == st->scale_avail[1][1]) + gain = true; + else + return -EINVAL; + + mutex_lock(&iio_dev->mlock); + + ret = st->ops->reg_read(st, AD5592R_REG_CTRL, + &st->cached_gp_ctrl); + if (ret < 0) { + mutex_unlock(&iio_dev->mlock); + return ret; + } + + if (chan->output) { + if (gain) + st->cached_gp_ctrl |= + AD5592R_REG_CTRL_DAC_RANGE; + else + st->cached_gp_ctrl &= + ~AD5592R_REG_CTRL_DAC_RANGE; + } else { + if (gain) + st->cached_gp_ctrl |= + AD5592R_REG_CTRL_ADC_RANGE; + else + st->cached_gp_ctrl &= + ~AD5592R_REG_CTRL_ADC_RANGE; + } + + ret = st->ops->reg_write(st, AD5592R_REG_CTRL, + st->cached_gp_ctrl); + mutex_unlock(&iio_dev->mlock); + + return ret; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad5592r_read_raw(struct iio_dev *iio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long m) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + u16 read_val; + int ret; + + switch (m) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&iio_dev->mlock); + + if (!chan->output) { + ret = st->ops->read_adc(st, chan->channel, &read_val); + if (ret) + goto unlock; + + if ((read_val >> 12 & 0x7) != (chan->channel & 0x7)) { + dev_err(st->dev, "Error while reading channel %u\n", + chan->channel); + ret = -EIO; + goto unlock; + } + + read_val &= GENMASK(11, 0); + + } else { + read_val = st->cached_dac[chan->channel]; + } + + dev_dbg(st->dev, "Channel %u read: 0x%04hX\n", + chan->channel, read_val); + + *val = (int) read_val; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + *val = ad5592r_get_vref(st); + + if (chan->type == IIO_TEMP) { + s64 tmp = *val * (3767897513LL / 25LL); + *val = div_s64_rem(tmp, 1000000000LL, val2); + + ret = IIO_VAL_INT_PLUS_MICRO; + } else { + int mult; + + mutex_lock(&iio_dev->mlock); + + if (chan->output) + mult = !!(st->cached_gp_ctrl & + AD5592R_REG_CTRL_DAC_RANGE); + else + mult = !!(st->cached_gp_ctrl & + AD5592R_REG_CTRL_ADC_RANGE); + + *val *= ++mult; + + *val2 = chan->scan_type.realbits; + ret = IIO_VAL_FRACTIONAL_LOG2; + } + break; + case IIO_CHAN_INFO_OFFSET: + ret = ad5592r_get_vref(st); + + mutex_lock(&iio_dev->mlock); + + if (st->cached_gp_ctrl & AD5592R_REG_CTRL_ADC_RANGE) + *val = (-34365 * 25) / ret; + else + *val = (-75365 * 25) / ret; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + +unlock: + mutex_unlock(&iio_dev->mlock); + return ret; +} + +static int ad5592r_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + + default: + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static const struct iio_info ad5592r_info = { + .read_raw = ad5592r_read_raw, + .write_raw = ad5592r_write_raw, + .write_raw_get_fmt = ad5592r_write_raw_get_fmt, + .driver_module = THIS_MODULE, +}; + +static ssize_t ad5592r_show_scale_available(struct iio_dev *iio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad5592r_state *st = iio_priv(iio_dev); + + return sprintf(buf, "%d.%09u %d.%09u\n", + st->scale_avail[0][0], st->scale_avail[0][1], + st->scale_avail[1][0], st->scale_avail[1][1]); +} + +static struct iio_chan_spec_ext_info ad5592r_ext_info[] = { + { + .name = "scale_available", + .read = ad5592r_show_scale_available, + .shared = true, + }, + {}, +}; + +static void ad5592r_setup_channel(struct iio_dev *iio_dev, + struct iio_chan_spec *chan, bool output, unsigned id) +{ + chan->type = IIO_VOLTAGE; + chan->indexed = 1; + chan->output = output; + chan->channel = id; + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW); + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->scan_type.sign = 'u'; + chan->scan_type.realbits = 12; + chan->scan_type.storagebits = 16; + chan->ext_info = ad5592r_ext_info; +} + +static int ad5592r_alloc_channels(struct ad5592r_state *st) +{ + unsigned i, curr_channel = 0, + num_channels = st->num_channels; + struct iio_dev *iio_dev = iio_priv_to_dev(st); + struct iio_chan_spec *channels; + struct fwnode_handle *child; + u32 reg, tmp; + int ret; + + device_for_each_child_node(st->dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret || reg >= ARRAY_SIZE(st->channel_modes)) + continue; + + ret = fwnode_property_read_u32(child, "adi,mode", &tmp); + if (!ret) + st->channel_modes[reg] = tmp; + + fwnode_property_read_u32(child, "adi,off-state", &tmp); + if (!ret) + st->channel_offstate[reg] = tmp; + } + + channels = devm_kzalloc(st->dev, + (1 + 2 * num_channels) * sizeof(*channels), GFP_KERNEL); + if (!channels) + return -ENOMEM; + + for (i = 0; i < num_channels; i++) { + switch (st->channel_modes[i]) { + case CH_MODE_DAC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + true, i); + curr_channel++; + break; + + case CH_MODE_ADC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + false, i); + curr_channel++; + break; + + case CH_MODE_DAC_AND_ADC: + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + true, i); + curr_channel++; + ad5592r_setup_channel(iio_dev, &channels[curr_channel], + false, i); + curr_channel++; + break; + + default: + continue; + } + } + + channels[curr_channel].type = IIO_TEMP; + channels[curr_channel].channel = 8; + channels[curr_channel].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OFFSET); + curr_channel++; + + iio_dev->num_channels = curr_channel; + iio_dev->channels = channels; + + return 0; +} + +static void ad5592r_init_scales(struct ad5592r_state *st, int vref_mV) +{ + s64 tmp = (s64)vref_mV * 1000000000LL >> 12; + + st->scale_avail[0][0] = + div_s64_rem(tmp, 1000000000LL, &st->scale_avail[0][1]); + st->scale_avail[1][0] = + div_s64_rem(tmp * 2, 1000000000LL, &st->scale_avail[1][1]); +} + +int ad5592r_probe(struct device *dev, const char *name, + const struct ad5592r_rw_ops *ops) +{ + struct iio_dev *iio_dev; + struct ad5592r_state *st; + int ret; + + iio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!iio_dev) + return -ENOMEM; + + st = iio_priv(iio_dev); + st->dev = dev; + st->ops = ops; + st->num_channels = 8; + dev_set_drvdata(dev, iio_dev); + + st->reg = devm_regulator_get_optional(dev, "vref"); + if (IS_ERR(st->reg)) { + if ((PTR_ERR(st->reg) != -ENODEV) && dev->of_node) + return PTR_ERR(st->reg); + + st->reg = NULL; + } else { + ret = regulator_enable(st->reg); + if (ret) + return ret; + } + + iio_dev->dev.parent = dev; + iio_dev->name = name; + iio_dev->info = &ad5592r_info; + iio_dev->modes = INDIO_DIRECT_MODE; + + ad5592r_init_scales(st, ad5592r_get_vref(st)); + + ret = ad5592r_reset(st); + if (ret) + goto error_disable_reg; + + ret = ops->reg_write(st, AD5592R_REG_PD, + (st->reg == NULL) ? AD5592R_REG_PD_EN_REF : 0); + if (ret) + goto error_disable_reg; + + ret = ad5592r_alloc_channels(st); + if (ret) + goto error_disable_reg; + + ret = ad5592r_set_channel_modes(st); + if (ret) + goto error_reset_ch_modes; + + ret = iio_device_register(iio_dev); + if (ret) + goto error_reset_ch_modes; + + ret = ad5592r_gpio_init(st); + if (ret) + goto error_dev_unregister; + + return 0; + +error_dev_unregister: + iio_device_unregister(iio_dev); + +error_reset_ch_modes: + ad5592r_reset_channel_modes(st); + +error_disable_reg: + if (st->reg) + regulator_disable(st->reg); + + return ret; +} +EXPORT_SYMBOL_GPL(ad5592r_probe); + +int ad5592r_remove(struct device *dev) +{ + struct iio_dev *iio_dev = dev_get_drvdata(dev); + struct ad5592r_state *st = iio_priv(iio_dev); + + iio_device_unregister(iio_dev); + ad5592r_reset_channel_modes(st); + ad5592r_gpio_cleanup(st); + + if (st->reg) + regulator_disable(st->reg); + + return 0; +} +EXPORT_SYMBOL_GPL(ad5592r_remove); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5592R multi-channel converters"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dac/ad5592r-base.h b/drivers/iio/dac/ad5592r-base.h new file mode 100644 index 0000000..841457e --- /dev/null +++ b/drivers/iio/dac/ad5592r-base.h @@ -0,0 +1,76 @@ +/* + * AD5592R / AD5593R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + * + * Licensed under the GPL-2. + */ + +#ifndef __DRIVERS_IIO_DAC_AD5592R_BASE_H__ +#define __DRIVERS_IIO_DAC_AD5592R_BASE_H__ + +#include +#include +#include +#include + +struct device; +struct ad5592r_state; + +enum ad5592r_registers { + AD5592R_REG_NOOP = 0x0, + AD5592R_REG_DAC_READBACK = 0x1, + AD5592R_REG_ADC_SEQ = 0x2, + AD5592R_REG_CTRL = 0x3, + AD5592R_REG_ADC_EN = 0x4, + AD5592R_REG_DAC_EN = 0x5, + AD5592R_REG_PULLDOWN = 0x6, + AD5592R_REG_LDAC = 0x7, + AD5592R_REG_GPIO_OUT_EN = 0x8, + AD5592R_REG_GPIO_SET = 0x9, + AD5592R_REG_GPIO_IN_EN = 0xA, + AD5592R_REG_PD = 0xB, + AD5592R_REG_OPEN_DRAIN = 0xC, + AD5592R_REG_TRISTATE = 0xD, + AD5592R_REG_RESET = 0xF, +}; + +#define AD5592R_REG_PD_EN_REF BIT(9) +#define AD5592R_REG_CTRL_ADC_RANGE BIT(5) +#define AD5592R_REG_CTRL_DAC_RANGE BIT(4) + +struct ad5592r_rw_ops { + int (*write_dac)(struct ad5592r_state *st, unsigned chan, u16 value); + int (*read_adc)(struct ad5592r_state *st, unsigned chan, u16 *value); + int (*reg_write)(struct ad5592r_state *st, u8 reg, u16 value); + int (*reg_read)(struct ad5592r_state *st, u8 reg, u16 *value); + int (*gpio_read)(struct ad5592r_state *st, u8 *value); +}; + +struct ad5592r_state { + struct device *dev; + struct regulator *reg; + struct gpio_chip gpiochip; + struct mutex gpio_lock; /* Protect cached gpio_out, gpio_val, etc. */ + unsigned int num_channels; + const struct ad5592r_rw_ops *ops; + int scale_avail[2][2]; + u16 cached_dac[8]; + u16 cached_gp_ctrl; + u8 channel_modes[8]; + u8 channel_offstate[8]; + u8 gpio_map; + u8 gpio_out; + u8 gpio_in; + u8 gpio_val; + + __be16 spi_msg ____cacheline_aligned; + __be16 spi_msg_nop; +}; + +int ad5592r_probe(struct device *dev, const char *name, + const struct ad5592r_rw_ops *ops); +int ad5592r_remove(struct device *dev); + +#endif /* __DRIVERS_IIO_DAC_AD5592R_BASE_H__ */ diff --git b/drivers/iio/dac/ad5592r.c b/drivers/iio/dac/ad5592r.c new file mode 100644 index 0000000..0b235a2 --- /dev/null +++ b/drivers/iio/dac/ad5592r.c @@ -0,0 +1,164 @@ +/* + * AD5592R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + * + * Licensed under the GPL-2. + */ + +#include "ad5592r-base.h" + +#include +#include +#include +#include + +#define AD5592R_GPIO_READBACK_EN BIT(10) +#define AD5592R_LDAC_READBACK_EN BIT(6) + +static int ad5592r_spi_wnop_r16(struct ad5592r_state *st, u16 *buf) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + struct spi_transfer t = { + .tx_buf = &st->spi_msg_nop, + .rx_buf = buf, + .len = 2 + }; + + st->spi_msg_nop = 0; /* NOP */ + + return spi_sync_transfer(spi, &t, 1); +} + +static int ad5592r_write_dac(struct ad5592r_state *st, unsigned chan, u16 value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + + st->spi_msg = cpu_to_be16(BIT(15) | (chan << 12) | value); + + return spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); +} + +static int ad5592r_read_adc(struct ad5592r_state *st, unsigned chan, u16 *value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + int ret; + + st->spi_msg = cpu_to_be16((AD5592R_REG_ADC_SEQ << 11) | BIT(chan)); + + ret = spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); + if (ret) + return ret; + + /* + * Invalid data: + * See Figure 40. Single-Channel ADC Conversion Sequence + */ + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = be16_to_cpu(st->spi_msg); + + return 0; +} + +static int ad5592r_reg_write(struct ad5592r_state *st, u8 reg, u16 value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + + st->spi_msg = cpu_to_be16((reg << 11) | value); + + return spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); +} + +static int ad5592r_reg_read(struct ad5592r_state *st, u8 reg, u16 *value) +{ + struct spi_device *spi = container_of(st->dev, struct spi_device, dev); + int ret; + + st->spi_msg = cpu_to_be16((AD5592R_REG_LDAC << 11) | + AD5592R_LDAC_READBACK_EN | (reg << 2)); + + ret = spi_write(spi, &st->spi_msg, sizeof(st->spi_msg)); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = be16_to_cpu(st->spi_msg); + + return 0; +} + +static int ad5593r_gpio_read(struct ad5592r_state *st, u8 *value) +{ + int ret; + + ret = ad5592r_reg_write(st, AD5592R_REG_GPIO_IN_EN, + AD5592R_GPIO_READBACK_EN | st->gpio_in); + if (ret) + return ret; + + ret = ad5592r_spi_wnop_r16(st, &st->spi_msg); + if (ret) + return ret; + + *value = (u8) be16_to_cpu(st->spi_msg); + + return 0; +} + +static const struct ad5592r_rw_ops ad5592r_rw_ops = { + .write_dac = ad5592r_write_dac, + .read_adc = ad5592r_read_adc, + .reg_write = ad5592r_reg_write, + .reg_read = ad5592r_reg_read, + .gpio_read = ad5593r_gpio_read, +}; + +static int ad5592r_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return ad5592r_probe(&spi->dev, id->name, &ad5592r_rw_ops); +} + +static int ad5592r_spi_remove(struct spi_device *spi) +{ + return ad5592r_remove(&spi->dev); +} + +static const struct spi_device_id ad5592r_spi_ids[] = { + { .name = "ad5592r", }, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5592r_spi_ids); + +static const struct of_device_id ad5592r_of_match[] = { + { .compatible = "adi,ad5592r", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ad5592r_of_match); + +static struct spi_driver ad5592r_spi_driver = { + .driver = { + .name = "ad5592r", + .of_match_table = of_match_ptr(ad5592r_of_match), + }, + .probe = ad5592r_spi_probe, + .remove = ad5592r_spi_remove, + .id_table = ad5592r_spi_ids, +}; +module_spi_driver(ad5592r_spi_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5592R multi-channel converters"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dac/ad5593r.c b/drivers/iio/dac/ad5593r.c new file mode 100644 index 0000000..dca158a --- /dev/null +++ b/drivers/iio/dac/ad5593r.c @@ -0,0 +1,131 @@ +/* + * AD5593R Digital <-> Analog converters driver + * + * Copyright 2015-2016 Analog Devices Inc. + * Author: Paul Cercueil + * + * Licensed under the GPL-2. + */ + +#include "ad5592r-base.h" + +#include +#include +#include +#include + +#define AD5593R_MODE_CONF (0 << 4) +#define AD5593R_MODE_DAC_WRITE (1 << 4) +#define AD5593R_MODE_ADC_READBACK (4 << 4) +#define AD5593R_MODE_DAC_READBACK (5 << 4) +#define AD5593R_MODE_GPIO_READBACK (6 << 4) +#define AD5593R_MODE_REG_READBACK (7 << 4) + +static int ad5593r_write_dac(struct ad5592r_state *st, unsigned chan, u16 value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + return i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_DAC_WRITE | chan, value); +} + +static int ad5593r_read_adc(struct ad5592r_state *st, unsigned chan, u16 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + s32 val; + + val = i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_CONF | AD5592R_REG_ADC_SEQ, BIT(chan)); + if (val < 0) + return (int) val; + + val = i2c_smbus_read_word_swapped(i2c, AD5593R_MODE_ADC_READBACK); + if (val < 0) + return (int) val; + + *value = (u16) val; + + return 0; +} + +static int ad5593r_reg_write(struct ad5592r_state *st, u8 reg, u16 value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + + return i2c_smbus_write_word_swapped(i2c, + AD5593R_MODE_CONF | reg, value); +} + +static int ad5593r_reg_read(struct ad5592r_state *st, u8 reg, u16 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + s32 val; + + val = i2c_smbus_read_word_swapped(i2c, AD5593R_MODE_REG_READBACK | reg); + if (val < 0) + return (int) val; + + *value = (u16) val; + + return 0; +} + +static int ad5593r_gpio_read(struct ad5592r_state *st, u8 *value) +{ + struct i2c_client *i2c = to_i2c_client(st->dev); + s32 val; + + val = i2c_smbus_read_word_swapped(i2c, AD5593R_MODE_GPIO_READBACK); + if (val < 0) + return (int) val; + + *value = (u8) val; + + return 0; +} + +static const struct ad5592r_rw_ops ad5593r_rw_ops = { + .write_dac = ad5593r_write_dac, + .read_adc = ad5593r_read_adc, + .reg_write = ad5593r_reg_write, + .reg_read = ad5593r_reg_read, + .gpio_read = ad5593r_gpio_read, +}; + +static int ad5593r_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + return ad5592r_probe(&i2c->dev, id->name, &ad5593r_rw_ops); +} + +static int ad5593r_i2c_remove(struct i2c_client *i2c) +{ + return ad5592r_remove(&i2c->dev); +} + +static const struct i2c_device_id ad5593r_i2c_ids[] = { + { .name = "ad5593r", }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, ad5593r_i2c_ids); + +static const struct of_device_id ad5593r_of_match[] = { + { .compatible = "adi,ad5593r", }, + {}, +}; +MODULE_DEVICE_TABLE(of, ad5593r_of_match); + +static struct i2c_driver ad5593r_driver = { + .driver = { + .name = "ad5593r", + .of_match_table = of_match_ptr(ad5593r_of_match), + }, + .probe = ad5593r_i2c_probe, + .remove = ad5593r_i2c_remove, + .id_table = ad5593r_i2c_ids, +}; +module_i2c_driver(ad5593r_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Analog Devices AD5592R multi-channel converters"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dac/ad5761.c b/drivers/iio/dac/ad5761.c new file mode 100644 index 0000000..d6510d6 --- /dev/null +++ b/drivers/iio/dac/ad5761.c @@ -0,0 +1,430 @@ +/* + * AD5721, AD5721R, AD5761, AD5761R, Voltage Output Digital to Analog Converter + * + * Copyright 2016 Qtechnology A/S + * 2016 Ricardo Ribalda + * + * Licensed under the GPL-2. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define AD5761_ADDR(addr) ((addr & 0xf) << 16) +#define AD5761_ADDR_NOOP 0x0 +#define AD5761_ADDR_DAC_WRITE 0x3 +#define AD5761_ADDR_CTRL_WRITE_REG 0x4 +#define AD5761_ADDR_SW_DATA_RESET 0x7 +#define AD5761_ADDR_DAC_READ 0xb +#define AD5761_ADDR_CTRL_READ_REG 0xc +#define AD5761_ADDR_SW_FULL_RESET 0xf + +#define AD5761_CTRL_USE_INTVREF BIT(5) +#define AD5761_CTRL_ETS BIT(6) + +/** + * struct ad5761_chip_info - chip specific information + * @int_vref: Value of the internal reference voltage in mV - 0 if external + * reference voltage is used + * @channel: channel specification +*/ + +struct ad5761_chip_info { + unsigned long int_vref; + const struct iio_chan_spec channel; +}; + +struct ad5761_range_params { + int m; + int c; +}; + +enum ad5761_supported_device_ids { + ID_AD5721, + ID_AD5721R, + ID_AD5761, + ID_AD5761R, +}; + +/** + * struct ad5761_state - driver instance specific data + * @spi: spi_device + * @vref_reg: reference voltage regulator + * @use_intref: true when the internal voltage reference is used + * @vref: actual voltage reference in mVolts + * @range: output range mode used + * @data: cache aligned spi buffer + */ +struct ad5761_state { + struct spi_device *spi; + struct regulator *vref_reg; + + bool use_intref; + int vref; + enum ad5761_voltage_range range; + + /* + * DMA (thus cache coherency maintenance) requires the + * transfer buffers to live in their own cache lines. + */ + union { + __be32 d32; + u8 d8[4]; + } data[3] ____cacheline_aligned; +}; + +static const struct ad5761_range_params ad5761_range_params[] = { + [AD5761_VOLTAGE_RANGE_M10V_10V] = { + .m = 80, + .c = 40, + }, + [AD5761_VOLTAGE_RANGE_0V_10V] = { + .m = 40, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_M5V_5V] = { + .m = 40, + .c = 20, + }, + [AD5761_VOLTAGE_RANGE_0V_5V] = { + .m = 20, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_M2V5_7V5] = { + .m = 40, + .c = 10, + }, + [AD5761_VOLTAGE_RANGE_M3V_3V] = { + .m = 24, + .c = 12, + }, + [AD5761_VOLTAGE_RANGE_0V_16V] = { + .m = 64, + .c = 0, + }, + [AD5761_VOLTAGE_RANGE_0V_20V] = { + .m = 80, + .c = 0, + }, +}; + +static int _ad5761_spi_write(struct ad5761_state *st, u8 addr, u16 val) +{ + st->data[0].d32 = cpu_to_be32(AD5761_ADDR(addr) | val); + + return spi_write(st->spi, &st->data[0].d8[1], 3); +} + +static int ad5761_spi_write(struct iio_dev *indio_dev, u8 addr, u16 val) +{ + struct ad5761_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + ret = _ad5761_spi_write(st, addr, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int _ad5761_spi_read(struct ad5761_state *st, u8 addr, u16 *val) +{ + int ret; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->data[0].d8[1], + .bits_per_word = 8, + .len = 3, + .cs_change = true, + }, { + .tx_buf = &st->data[1].d8[1], + .rx_buf = &st->data[2].d8[1], + .bits_per_word = 8, + .len = 3, + }, + }; + + st->data[0].d32 = cpu_to_be32(AD5761_ADDR(addr)); + st->data[1].d32 = cpu_to_be32(AD5761_ADDR(AD5761_ADDR_NOOP)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + + *val = be32_to_cpu(st->data[2].d32); + + return ret; +} + +static int ad5761_spi_read(struct iio_dev *indio_dev, u8 addr, u16 *val) +{ + struct ad5761_state *st = iio_priv(indio_dev); + int ret; + + mutex_lock(&indio_dev->mlock); + ret = _ad5761_spi_read(st, addr, val); + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int ad5761_spi_set_range(struct ad5761_state *st, + enum ad5761_voltage_range range) +{ + u16 aux; + int ret; + + aux = (range & 0x7) | AD5761_CTRL_ETS; + + if (st->use_intref) + aux |= AD5761_CTRL_USE_INTVREF; + + ret = _ad5761_spi_write(st, AD5761_ADDR_SW_FULL_RESET, 0); + if (ret) + return ret; + + ret = _ad5761_spi_write(st, AD5761_ADDR_CTRL_WRITE_REG, aux); + if (ret) + return ret; + + st->range = range; + + return 0; +} + +static int ad5761_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct ad5761_state *st; + int ret; + u16 aux; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ad5761_spi_read(indio_dev, AD5761_ADDR_DAC_READ, &aux); + if (ret) + return ret; + *val = aux >> chan->scan_type.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + st = iio_priv(indio_dev); + *val = st->vref * ad5761_range_params[st->range].m; + *val /= 10; + *val2 = chan->scan_type.realbits; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + st = iio_priv(indio_dev); + *val = -(1 << chan->scan_type.realbits); + *val *= ad5761_range_params[st->range].c; + *val /= ad5761_range_params[st->range].m; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad5761_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + u16 aux; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val2 || (val << chan->scan_type.shift) > 0xffff || val < 0) + return -EINVAL; + + aux = val << chan->scan_type.shift; + + return ad5761_spi_write(indio_dev, AD5761_ADDR_DAC_WRITE, aux); +} + +static const struct iio_info ad5761_info = { + .read_raw = &ad5761_read_raw, + .write_raw = &ad5761_write_raw, + .driver_module = THIS_MODULE, +}; + +#define AD5761_CHAN(_bits) { \ + .type = IIO_VOLTAGE, \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (_bits), \ + .storagebits = 16, \ + .shift = 16 - (_bits), \ + }, \ +} + +static const struct ad5761_chip_info ad5761_chip_infos[] = { + [ID_AD5721] = { + .int_vref = 0, + .channel = AD5761_CHAN(12), + }, + [ID_AD5721R] = { + .int_vref = 2500, + .channel = AD5761_CHAN(12), + }, + [ID_AD5761] = { + .int_vref = 0, + .channel = AD5761_CHAN(16), + }, + [ID_AD5761R] = { + .int_vref = 2500, + .channel = AD5761_CHAN(16), + }, +}; + +static int ad5761_get_vref(struct ad5761_state *st, + const struct ad5761_chip_info *chip_info) +{ + int ret; + + st->vref_reg = devm_regulator_get_optional(&st->spi->dev, "vref"); + if (PTR_ERR(st->vref_reg) == -ENODEV) { + /* Use Internal regulator */ + if (!chip_info->int_vref) { + dev_err(&st->spi->dev, + "Voltage reference not found\n"); + return -EIO; + } + + st->use_intref = true; + st->vref = chip_info->int_vref; + return 0; + } + + if (IS_ERR(st->vref_reg)) { + dev_err(&st->spi->dev, + "Error getting voltage reference regulator\n"); + return PTR_ERR(st->vref_reg); + } + + ret = regulator_enable(st->vref_reg); + if (ret) { + dev_err(&st->spi->dev, + "Failed to enable voltage reference\n"); + return ret; + } + + ret = regulator_get_voltage(st->vref_reg); + if (ret < 0) { + dev_err(&st->spi->dev, + "Failed to get voltage reference value\n"); + goto disable_regulator_vref; + } + + if (ret < 2000000 || ret > 3000000) { + dev_warn(&st->spi->dev, + "Invalid external voltage ref. value %d uV\n", ret); + ret = -EIO; + goto disable_regulator_vref; + } + + st->vref = ret / 1000; + st->use_intref = false; + + return 0; + +disable_regulator_vref: + regulator_disable(st->vref_reg); + st->vref_reg = NULL; + return ret; +} + +static int ad5761_probe(struct spi_device *spi) +{ + struct iio_dev *iio_dev; + struct ad5761_state *st; + int ret; + const struct ad5761_chip_info *chip_info = + &ad5761_chip_infos[spi_get_device_id(spi)->driver_data]; + enum ad5761_voltage_range voltage_range = AD5761_VOLTAGE_RANGE_0V_5V; + struct ad5761_platform_data *pdata = dev_get_platdata(&spi->dev); + + iio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!iio_dev) + return -ENOMEM; + + st = iio_priv(iio_dev); + + st->spi = spi; + spi_set_drvdata(spi, iio_dev); + + ret = ad5761_get_vref(st, chip_info); + if (ret) + return ret; + + if (pdata) + voltage_range = pdata->voltage_range; + + ret = ad5761_spi_set_range(st, voltage_range); + if (ret) + goto disable_regulator_err; + + iio_dev->dev.parent = &spi->dev; + iio_dev->info = &ad5761_info; + iio_dev->modes = INDIO_DIRECT_MODE; + iio_dev->channels = &chip_info->channel; + iio_dev->num_channels = 1; + iio_dev->name = spi_get_device_id(st->spi)->name; + ret = iio_device_register(iio_dev); + if (ret) + goto disable_regulator_err; + + return 0; + +disable_regulator_err: + if (!IS_ERR_OR_NULL(st->vref_reg)) + regulator_disable(st->vref_reg); + + return ret; +} + +static int ad5761_remove(struct spi_device *spi) +{ + struct iio_dev *iio_dev = spi_get_drvdata(spi); + struct ad5761_state *st = iio_priv(iio_dev); + + iio_device_unregister(iio_dev); + + if (!IS_ERR_OR_NULL(st->vref_reg)) + regulator_disable(st->vref_reg); + + return 0; +} + +static const struct spi_device_id ad5761_id[] = { + {"ad5721", ID_AD5721}, + {"ad5721r", ID_AD5721R}, + {"ad5761", ID_AD5761}, + {"ad5761r", ID_AD5761R}, + {} +}; +MODULE_DEVICE_TABLE(spi, ad5761_id); + +static struct spi_driver ad5761_driver = { + .driver = { + .name = "ad5761", + }, + .probe = ad5761_probe, + .remove = ad5761_remove, + .id_table = ad5761_id, +}; +module_spi_driver(ad5761_driver); + +MODULE_AUTHOR("Ricardo Ribalda "); +MODULE_DESCRIPTION("Analog Devices AD5721, AD5721R, AD5761, AD5761R driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dac/lpc18xx_dac.c b/drivers/iio/dac/lpc18xx_dac.c new file mode 100644 index 0000000..55d1456 --- /dev/null +++ b/drivers/iio/dac/lpc18xx_dac.c @@ -0,0 +1,210 @@ +/* + * IIO DAC driver for NXP LPC18xx DAC + * + * Copyright (C) 2016 Joachim Eastwood + * + * 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. + * + * UNSUPPORTED hardware features: + * - Interrupts + * - DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* LPC18XX DAC registers and bits */ +#define LPC18XX_DAC_CR 0x000 +#define LPC18XX_DAC_CR_VALUE_SHIFT 6 +#define LPC18XX_DAC_CR_VALUE_MASK 0x3ff +#define LPC18XX_DAC_CR_BIAS BIT(16) +#define LPC18XX_DAC_CTRL 0x004 +#define LPC18XX_DAC_CTRL_DMA_ENA BIT(3) + +struct lpc18xx_dac { + struct regulator *vref; + void __iomem *base; + struct mutex lock; + struct clk *clk; +}; + +static const struct iio_chan_spec lpc18xx_dac_iio_channels[] = { + { + .type = IIO_VOLTAGE, + .output = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int lpc18xx_dac_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct lpc18xx_dac *dac = iio_priv(indio_dev); + u32 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + reg = readl(dac->base + LPC18XX_DAC_CR); + *val = reg >> LPC18XX_DAC_CR_VALUE_SHIFT; + *val &= LPC18XX_DAC_CR_VALUE_MASK; + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = regulator_get_voltage(dac->vref) / 1000; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + } + + return -EINVAL; +} + +static int lpc18xx_dac_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct lpc18xx_dac *dac = iio_priv(indio_dev); + u32 reg; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val < 0 || val > LPC18XX_DAC_CR_VALUE_MASK) + return -EINVAL; + + reg = LPC18XX_DAC_CR_BIAS; + reg |= val << LPC18XX_DAC_CR_VALUE_SHIFT; + + mutex_lock(&dac->lock); + writel(reg, dac->base + LPC18XX_DAC_CR); + writel(LPC18XX_DAC_CTRL_DMA_ENA, dac->base + LPC18XX_DAC_CTRL); + mutex_unlock(&dac->lock); + + return 0; + } + + return -EINVAL; +} + +static const struct iio_info lpc18xx_dac_info = { + .read_raw = lpc18xx_dac_read_raw, + .write_raw = lpc18xx_dac_write_raw, + .driver_module = THIS_MODULE, +}; + +static int lpc18xx_dac_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct lpc18xx_dac *dac; + struct resource *res; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*dac)); + if (!indio_dev) + return -ENOMEM; + + platform_set_drvdata(pdev, indio_dev); + dac = iio_priv(indio_dev); + mutex_init(&dac->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dac->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dac->base)) + return PTR_ERR(dac->base); + + dac->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dac->clk)) { + dev_err(&pdev->dev, "error getting clock\n"); + return PTR_ERR(dac->clk); + } + + dac->vref = devm_regulator_get(&pdev->dev, "vref"); + if (IS_ERR(dac->vref)) { + dev_err(&pdev->dev, "error getting regulator\n"); + return PTR_ERR(dac->vref); + } + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->info = &lpc18xx_dac_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = lpc18xx_dac_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(lpc18xx_dac_iio_channels); + + ret = regulator_enable(dac->vref); + if (ret) { + dev_err(&pdev->dev, "unable to enable regulator\n"); + return ret; + } + + ret = clk_prepare_enable(dac->clk); + if (ret) { + dev_err(&pdev->dev, "unable to enable clock\n"); + goto dis_reg; + } + + writel(0, dac->base + LPC18XX_DAC_CTRL); + writel(0, dac->base + LPC18XX_DAC_CR); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "unable to register device\n"); + goto dis_clk; + } + + return 0; + +dis_clk: + clk_disable_unprepare(dac->clk); +dis_reg: + regulator_disable(dac->vref); + return ret; +} + +static int lpc18xx_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct lpc18xx_dac *dac = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + + writel(0, dac->base + LPC18XX_DAC_CTRL); + clk_disable_unprepare(dac->clk); + regulator_disable(dac->vref); + + return 0; +} + +static const struct of_device_id lpc18xx_dac_match[] = { + { .compatible = "nxp,lpc1850-dac" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpc18xx_dac_match); + +static struct platform_driver lpc18xx_dac_driver = { + .probe = lpc18xx_dac_probe, + .remove = lpc18xx_dac_remove, + .driver = { + .name = "lpc18xx-dac", + .of_match_table = lpc18xx_dac_match, + }, +}; +module_platform_driver(lpc18xx_dac_driver); + +MODULE_DESCRIPTION("LPC18xx DAC driver"); +MODULE_AUTHOR("Joachim Eastwood "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/mcp4725.c b/drivers/iio/dac/mcp4725.c index b4dde83..cca935c 100644 --- a/drivers/iio/dac/mcp4725.c +++ b/drivers/iio/dac/mcp4725.c @@ -1,5 +1,5 @@ /* - * mcp4725.c - Support for Microchip MCP4725 + * mcp4725.c - Support for Microchip MCP4725/6 * * Copyright (C) 2012 Peter Meerwald * @@ -134,6 +134,12 @@ static const char * const mcp4725_powerdown_modes[] = { "500kohm_to_gnd" }; +static const char * const mcp4726_powerdown_modes[] = { + "1kohm_to_gnd", + "125kohm_to_gnd", + "640kohm_to_gnd" +}; + static int mcp4725_get_powerdown_mode(struct iio_dev *indio_dev, const struct iio_chan_spec *chan) { @@ -182,11 +188,24 @@ static ssize_t mcp4725_write_powerdown(struct iio_dev *indio_dev, return len; } -static const struct iio_enum mcp4725_powerdown_mode_enum = { - .items = mcp4725_powerdown_modes, - .num_items = ARRAY_SIZE(mcp4725_powerdown_modes), - .get = mcp4725_get_powerdown_mode, - .set = mcp4725_set_powerdown_mode, +enum { + MCP4725, + MCP4726, +}; + +static const struct iio_enum mcp472x_powerdown_mode_enum[] = { + [MCP4725] = { + .items = mcp4725_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4725_powerdown_modes), + .get = mcp4725_get_powerdown_mode, + .set = mcp4725_set_powerdown_mode, + }, + [MCP4726] = { + .items = mcp4726_powerdown_modes, + .num_items = ARRAY_SIZE(mcp4726_powerdown_modes), + .get = mcp4725_get_powerdown_mode, + .set = mcp4725_set_powerdown_mode, + }, }; static const struct iio_chan_spec_ext_info mcp4725_ext_info[] = { @@ -196,19 +215,46 @@ static const struct iio_chan_spec_ext_info mcp4725_ext_info[] = { .write = mcp4725_write_powerdown, .shared = IIO_SEPARATE, }, - IIO_ENUM("powerdown_mode", IIO_SEPARATE, &mcp4725_powerdown_mode_enum), - IIO_ENUM_AVAILABLE("powerdown_mode", &mcp4725_powerdown_mode_enum), + IIO_ENUM("powerdown_mode", IIO_SEPARATE, + &mcp472x_powerdown_mode_enum[MCP4725]), + IIO_ENUM_AVAILABLE("powerdown_mode", + &mcp472x_powerdown_mode_enum[MCP4725]), + { }, +}; + +static const struct iio_chan_spec_ext_info mcp4726_ext_info[] = { + { + .name = "powerdown", + .read = mcp4725_read_powerdown, + .write = mcp4725_write_powerdown, + .shared = IIO_SEPARATE, + }, + IIO_ENUM("powerdown_mode", IIO_SEPARATE, + &mcp472x_powerdown_mode_enum[MCP4726]), + IIO_ENUM_AVAILABLE("powerdown_mode", + &mcp472x_powerdown_mode_enum[MCP4726]), { }, }; -static const struct iio_chan_spec mcp4725_channel = { - .type = IIO_VOLTAGE, - .indexed = 1, - .output = 1, - .channel = 0, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), - .ext_info = mcp4725_ext_info, +static const struct iio_chan_spec mcp472x_channel[] = { + [MCP4725] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp4725_ext_info, + }, + [MCP4726] = { + .type = IIO_VOLTAGE, + .indexed = 1, + .output = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .ext_info = mcp4726_ext_info, + }, }; static int mcp4725_set_value(struct iio_dev *indio_dev, int val) @@ -302,7 +348,7 @@ static int mcp4725_probe(struct i2c_client *client, indio_dev->dev.parent = &client->dev; indio_dev->name = id->name; indio_dev->info = &mcp4725_info; - indio_dev->channels = &mcp4725_channel; + indio_dev->channels = &mcp472x_channel[id->driver_data]; indio_dev->num_channels = 1; indio_dev->modes = INDIO_DIRECT_MODE; @@ -316,7 +362,7 @@ static int mcp4725_probe(struct i2c_client *client, } pd = (inbuf[0] >> 1) & 0x3; data->powerdown = pd > 0 ? true : false; - data->powerdown_mode = pd ? pd-1 : 2; /* 500kohm_to_gnd */ + data->powerdown_mode = pd ? pd - 1 : 2; /* largest register to gnd */ data->dac_value = (inbuf[1] << 4) | (inbuf[2] >> 4); return iio_device_register(indio_dev); @@ -329,7 +375,8 @@ static int mcp4725_remove(struct i2c_client *client) } static const struct i2c_device_id mcp4725_id[] = { - { "mcp4725", 0 }, + { "mcp4725", MCP4725 }, + { "mcp4726", MCP4726 }, { } }; MODULE_DEVICE_TABLE(i2c, mcp4725_id); @@ -346,5 +393,5 @@ static struct i2c_driver mcp4725_driver = { module_i2c_driver(mcp4725_driver); MODULE_AUTHOR("Peter Meerwald "); -MODULE_DESCRIPTION("MCP4725 12-bit DAC"); +MODULE_DESCRIPTION("MCP4725/6 12-bit DAC"); MODULE_LICENSE("GPL"); diff --git b/drivers/iio/dac/stx104.c b/drivers/iio/dac/stx104.c new file mode 100644 index 0000000..2794122 --- /dev/null +++ b/drivers/iio/dac/stx104.c @@ -0,0 +1,134 @@ +/* + * DAC driver for the Apex Embedded Systems STX104 + * Copyright (C) 2016 William Breathitt Gray + * + * 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STX104_NUM_CHAN 2 + +#define STX104_CHAN(chan) { \ + .type = IIO_VOLTAGE, \ + .channel = chan, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .indexed = 1, \ + .output = 1 \ +} + +#define STX104_EXTENT 16 + +static unsigned int base[max_num_isa_dev(STX104_EXTENT)]; +static unsigned int num_stx104; +module_param_array(base, uint, &num_stx104, 0); +MODULE_PARM_DESC(base, "Apex Embedded Systems STX104 base addresses"); + +/** + * struct stx104_iio - IIO device private data structure + * @chan_out_states: channels' output states + * @base: base port address of the IIO device + */ +struct stx104_iio { + unsigned chan_out_states[STX104_NUM_CHAN]; + unsigned base; +}; + +static int stx104_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + *val = priv->chan_out_states[chan->channel]; + + return IIO_VAL_INT; +} + +static int stx104_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, long mask) +{ + struct stx104_iio *const priv = iio_priv(indio_dev); + const unsigned chan_addr_offset = 2 * chan->channel; + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + priv->chan_out_states[chan->channel] = val; + outw(val, priv->base + 4 + chan_addr_offset); + + return 0; +} + +static const struct iio_info stx104_info = { + .driver_module = THIS_MODULE, + .read_raw = stx104_read_raw, + .write_raw = stx104_write_raw +}; + +static const struct iio_chan_spec stx104_channels[STX104_NUM_CHAN] = { + STX104_CHAN(0), + STX104_CHAN(1) +}; + +static int stx104_probe(struct device *dev, unsigned int id) +{ + struct iio_dev *indio_dev; + struct stx104_iio *priv; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + if (!devm_request_region(dev, base[id], STX104_EXTENT, + dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + base[id], base[id] + STX104_EXTENT); + return -EBUSY; + } + + indio_dev->info = &stx104_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = stx104_channels; + indio_dev->num_channels = STX104_NUM_CHAN; + indio_dev->name = dev_name(dev); + + priv = iio_priv(indio_dev); + priv->base = base[id]; + + /* initialize DAC output to 0V */ + outw(0, base[id] + 4); + outw(0, base[id] + 6); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct isa_driver stx104_driver = { + .probe = stx104_probe, + .driver = { + .name = "stx104" + } +}; + +module_isa_driver(stx104_driver, num_stx104); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("Apex Embedded Systems STX104 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dac/vf610_dac.c b/drivers/iio/dac/vf610_dac.c new file mode 100644 index 0000000..c4ec777 --- /dev/null +++ b/drivers/iio/dac/vf610_dac.c @@ -0,0 +1,298 @@ +/* + * Freescale Vybrid vf610 DAC driver + * + * Copyright 2016 Toradex AG + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VF610_DACx_STATCTRL 0x20 + +#define VF610_DAC_DACEN BIT(15) +#define VF610_DAC_DACRFS BIT(14) +#define VF610_DAC_LPEN BIT(11) + +#define VF610_DAC_DAT0(x) ((x) & 0xFFF) + +enum vf610_conversion_mode_sel { + VF610_DAC_CONV_HIGH_POWER, + VF610_DAC_CONV_LOW_POWER, +}; + +struct vf610_dac { + struct clk *clk; + struct device *dev; + enum vf610_conversion_mode_sel conv_mode; + void __iomem *regs; +}; + +static void vf610_dac_init(struct vf610_dac *info) +{ + int val; + + info->conv_mode = VF610_DAC_CONV_LOW_POWER; + val = VF610_DAC_DACEN | VF610_DAC_DACRFS | + VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static void vf610_dac_exit(struct vf610_dac *info) +{ + int val; + + val = readl(info->regs + VF610_DACx_STATCTRL); + val &= ~VF610_DAC_DACEN; + writel(val, info->regs + VF610_DACx_STATCTRL); +} + +static int vf610_set_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int mode) +{ + struct vf610_dac *info = iio_priv(indio_dev); + int val; + + mutex_lock(&indio_dev->mlock); + info->conv_mode = mode; + val = readl(info->regs + VF610_DACx_STATCTRL); + if (mode) + val |= VF610_DAC_LPEN; + else + val &= ~VF610_DAC_LPEN; + writel(val, info->regs + VF610_DACx_STATCTRL); + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static int vf610_get_conversion_mode(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + return info->conv_mode; +} + +static const char * const vf610_conv_modes[] = { "high-power", "low-power" }; + +static const struct iio_enum vf610_conversion_mode = { + .items = vf610_conv_modes, + .num_items = ARRAY_SIZE(vf610_conv_modes), + .get = vf610_get_conversion_mode, + .set = vf610_set_conversion_mode, +}; + +static const struct iio_chan_spec_ext_info vf610_ext_info[] = { + IIO_ENUM("conversion_mode", IIO_SHARED_BY_DIR, + &vf610_conversion_mode), + {}, +}; + +#define VF610_DAC_CHAN(_chan_type) { \ + .type = (_chan_type), \ + .output = 1, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .ext_info = vf610_ext_info, \ +} + +static const struct iio_chan_spec vf610_dac_iio_channels[] = { + VF610_DAC_CHAN(IIO_VOLTAGE), +}; + +static int vf610_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = VF610_DAC_DAT0(readl(info->regs)); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + /* + * DACRFS is always 1 for valid reference and typical + * reference voltage as per Vybrid datasheet is 3.3V + * from section 9.1.2.1 of Vybrid datasheet + */ + *val = 3300 /* mV */; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static int vf610_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, + long mask) +{ + struct vf610_dac *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + writel(VF610_DAC_DAT0(val), info->regs); + mutex_unlock(&indio_dev->mlock); + return 0; + + default: + return -EINVAL; + } +} + +static const struct iio_info vf610_dac_iio_info = { + .driver_module = THIS_MODULE, + .read_raw = &vf610_read_raw, + .write_raw = &vf610_write_raw, +}; + +static const struct of_device_id vf610_dac_match[] = { + { .compatible = "fsl,vf610-dac", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_dac_match); + +static int vf610_dac_probe(struct platform_device *pdev) +{ + struct iio_dev *indio_dev; + struct vf610_dac *info; + struct resource *mem; + int ret; + + indio_dev = devm_iio_device_alloc(&pdev->dev, + sizeof(struct vf610_dac)); + if (!indio_dev) { + dev_err(&pdev->dev, "Failed allocating iio device\n"); + return -ENOMEM; + } + + info = iio_priv(indio_dev); + info->dev = &pdev->dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + info->clk = devm_clk_get(&pdev->dev, "dac"); + if (IS_ERR(info->clk)) { + dev_err(&pdev->dev, "Failed getting clock, err = %ld\n", + PTR_ERR(info->clk)); + return PTR_ERR(info->clk); + } + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &vf610_dac_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = vf610_dac_iio_channels; + indio_dev->num_channels = ARRAY_SIZE(vf610_dac_iio_channels); + + ret = clk_prepare_enable(info->clk); + if (ret) { + dev_err(&pdev->dev, + "Could not prepare or enable the clock\n"); + return ret; + } + + vf610_dac_init(info); + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(&pdev->dev, "Couldn't register the device\n"); + goto error_iio_device_register; + } + + return 0; + +error_iio_device_register: + clk_disable_unprepare(info->clk); + + return ret; +} + +static int vf610_dac_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + struct vf610_dac *info = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + vf610_dac_exit(info); + clk_disable_unprepare(info->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vf610_dac_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + + vf610_dac_exit(info); + clk_disable_unprepare(info->clk); + + return 0; +} + +static int vf610_dac_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct vf610_dac *info = iio_priv(indio_dev); + int ret; + + ret = clk_prepare_enable(info->clk); + if (ret) + return ret; + + vf610_dac_init(info); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vf610_dac_pm_ops, vf610_dac_suspend, vf610_dac_resume); + +static struct platform_driver vf610_dac_driver = { + .probe = vf610_dac_probe, + .remove = vf610_dac_remove, + .driver = { + .name = "vf610-dac", + .of_match_table = vf610_dac_match, + .pm = &vf610_dac_pm_ops, + }, +}; +module_platform_driver(vf610_dac_driver); + +MODULE_AUTHOR("Sanchayan Maity "); +MODULE_DESCRIPTION("Freescale VF610 DAC driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dummy/Kconfig b/drivers/iio/dummy/Kconfig new file mode 100644 index 0000000..71805ce --- /dev/null +++ b/drivers/iio/dummy/Kconfig @@ -0,0 +1,36 @@ +# +# Industrial I/O subsystem Dummy Driver configuration +# +menu "IIO dummy driver" + depends on IIO + +config IIO_DUMMY_EVGEN + select IRQ_WORK + tristate + +config IIO_SIMPLE_DUMMY + tristate "An example driver with no hardware requirements" + help + Driver intended mainly as documentation for how to write + a driver. May also be useful for testing userspace code + without hardware. + +if IIO_SIMPLE_DUMMY + +config IIO_SIMPLE_DUMMY_EVENTS + bool "Event generation support" + select IIO_DUMMY_EVGEN + help + Add some dummy events to the simple dummy driver. + +config IIO_SIMPLE_DUMMY_BUFFER + bool "Buffered capture support" + select IIO_BUFFER + select IIO_TRIGGER + select IIO_KFIFO_BUF + help + Add buffered data capture to the simple dummy driver. + +endif # IIO_SIMPLE_DUMMY + +endmenu diff --git b/drivers/iio/dummy/Makefile b/drivers/iio/dummy/Makefile new file mode 100644 index 0000000..0765e93 --- /dev/null +++ b/drivers/iio/dummy/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the IIO Dummy Driver +# + +obj-$(CONFIG_IIO_SIMPLE_DUMMY) += iio_dummy.o +iio_dummy-y := iio_simple_dummy.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_EVENTS) += iio_simple_dummy_events.o +iio_dummy-$(CONFIG_IIO_SIMPLE_DUMMY_BUFFER) += iio_simple_dummy_buffer.o + +obj-$(CONFIG_IIO_DUMMY_EVGEN) += iio_dummy_evgen.o diff --git b/drivers/iio/dummy/iio_dummy_evgen.c b/drivers/iio/dummy/iio_dummy_evgen.c new file mode 100644 index 0000000..9e83f34 --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.c @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * 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. + * + * Companion module to the iio simple dummy example driver. + * The purpose of this is to generate 'fake' event interrupts thus + * allowing that driver's code to be as close as possible to that of + * a normal driver talking to hardware. The approach used here + * is not intended to be general and just happens to work for this + * particular use case. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "iio_dummy_evgen.h" +#include +#include +#include + +/* Fiddly bit of faking and irq without hardware */ +#define IIO_EVENTGEN_NO 10 + +/** + * struct iio_dummy_handle_irq - helper struct to simulate interrupt generation + * @work: irq_work used to run handlers from hardirq context + * @irq: fake irq line number to trigger an interrupt + */ +struct iio_dummy_handle_irq { + struct irq_work work; + int irq; +}; + +/** + * struct iio_dummy_evgen - evgen state + * @chip: irq chip we are faking + * @base: base of irq range + * @enabled: mask of which irqs are enabled + * @inuse: mask of which irqs are connected + * @regs: irq regs we are faking + * @lock: protect the evgen state + * @handler: helper for a 'hardware-like' interrupt simulation + */ +struct iio_dummy_eventgen { + struct irq_chip chip; + int base; + bool enabled[IIO_EVENTGEN_NO]; + bool inuse[IIO_EVENTGEN_NO]; + struct iio_dummy_regs regs[IIO_EVENTGEN_NO]; + struct mutex lock; + struct iio_dummy_handle_irq handler; +}; + +/* We can only ever have one instance of this 'device' */ +static struct iio_dummy_eventgen *iio_evgen; +static const char *iio_evgen_name = "iio_dummy_evgen"; + +static void iio_dummy_event_irqmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = false; +} + +static void iio_dummy_event_irqunmask(struct irq_data *d) +{ + struct irq_chip *chip = irq_data_get_irq_chip(d); + struct iio_dummy_eventgen *evgen = + container_of(chip, struct iio_dummy_eventgen, chip); + + evgen->enabled[d->irq - evgen->base] = true; +} + +static void iio_dummy_work_handler(struct irq_work *work) +{ + struct iio_dummy_handle_irq *irq_handler; + + irq_handler = container_of(work, struct iio_dummy_handle_irq, work); + handle_simple_irq(irq_to_desc(irq_handler->irq)); +} + +static int iio_dummy_evgen_create(void) +{ + int ret, i; + + iio_evgen = kzalloc(sizeof(*iio_evgen), GFP_KERNEL); + if (!iio_evgen) + return -ENOMEM; + + iio_evgen->base = irq_alloc_descs(-1, 0, IIO_EVENTGEN_NO, 0); + if (iio_evgen->base < 0) { + ret = iio_evgen->base; + kfree(iio_evgen); + return ret; + } + iio_evgen->chip.name = iio_evgen_name; + iio_evgen->chip.irq_mask = &iio_dummy_event_irqmask; + iio_evgen->chip.irq_unmask = &iio_dummy_event_irqunmask; + for (i = 0; i < IIO_EVENTGEN_NO; i++) { + irq_set_chip(iio_evgen->base + i, &iio_evgen->chip); + irq_set_handler(iio_evgen->base + i, &handle_simple_irq); + irq_modify_status(iio_evgen->base + i, + IRQ_NOREQUEST | IRQ_NOAUTOEN, + IRQ_NOPROBE); + } + init_irq_work(&iio_evgen->handler.work, iio_dummy_work_handler); + mutex_init(&iio_evgen->lock); + return 0; +} + +/** + * iio_dummy_evgen_get_irq() - get an evgen provided irq for a device + * + * This function will give a free allocated irq to a client device. + * That irq can then be caused to 'fire' by using the associated sysfs file. + */ +int iio_dummy_evgen_get_irq(void) +{ + int i, ret = 0; + + if (!iio_evgen) + return -ENODEV; + + mutex_lock(&iio_evgen->lock); + for (i = 0; i < IIO_EVENTGEN_NO; i++) + if (!iio_evgen->inuse[i]) { + ret = iio_evgen->base + i; + iio_evgen->inuse[i] = true; + break; + } + mutex_unlock(&iio_evgen->lock); + if (i == IIO_EVENTGEN_NO) + return -ENOMEM; + return ret; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_irq); + +/** + * iio_dummy_evgen_release_irq() - give the irq back. + * @irq: irq being returned to the pool + * + * Used by client driver instances to give the irqs back when they disconnect + */ +void iio_dummy_evgen_release_irq(int irq) +{ + mutex_lock(&iio_evgen->lock); + iio_evgen->inuse[irq - iio_evgen->base] = false; + mutex_unlock(&iio_evgen->lock); +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_release_irq); + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq) +{ + return &iio_evgen->regs[irq - iio_evgen->base]; +} +EXPORT_SYMBOL_GPL(iio_dummy_evgen_get_regs); + +static void iio_dummy_evgen_free(void) +{ + irq_free_descs(iio_evgen->base, IIO_EVENTGEN_NO); + kfree(iio_evgen); +} + +static void iio_evgen_release(struct device *dev) +{ + iio_dummy_evgen_free(); +} + +static ssize_t iio_evgen_poke(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + unsigned long event; + int ret; + + ret = kstrtoul(buf, 10, &event); + if (ret) + return ret; + + iio_evgen->regs[this_attr->address].reg_id = this_attr->address; + iio_evgen->regs[this_attr->address].reg_data = event; + + iio_evgen->handler.irq = iio_evgen->base + this_attr->address; + if (iio_evgen->enabled[this_attr->address]) + irq_work_queue(&iio_evgen->handler.work); + + return len; +} + +static IIO_DEVICE_ATTR(poke_ev0, S_IWUSR, NULL, &iio_evgen_poke, 0); +static IIO_DEVICE_ATTR(poke_ev1, S_IWUSR, NULL, &iio_evgen_poke, 1); +static IIO_DEVICE_ATTR(poke_ev2, S_IWUSR, NULL, &iio_evgen_poke, 2); +static IIO_DEVICE_ATTR(poke_ev3, S_IWUSR, NULL, &iio_evgen_poke, 3); +static IIO_DEVICE_ATTR(poke_ev4, S_IWUSR, NULL, &iio_evgen_poke, 4); +static IIO_DEVICE_ATTR(poke_ev5, S_IWUSR, NULL, &iio_evgen_poke, 5); +static IIO_DEVICE_ATTR(poke_ev6, S_IWUSR, NULL, &iio_evgen_poke, 6); +static IIO_DEVICE_ATTR(poke_ev7, S_IWUSR, NULL, &iio_evgen_poke, 7); +static IIO_DEVICE_ATTR(poke_ev8, S_IWUSR, NULL, &iio_evgen_poke, 8); +static IIO_DEVICE_ATTR(poke_ev9, S_IWUSR, NULL, &iio_evgen_poke, 9); + +static struct attribute *iio_evgen_attrs[] = { + &iio_dev_attr_poke_ev0.dev_attr.attr, + &iio_dev_attr_poke_ev1.dev_attr.attr, + &iio_dev_attr_poke_ev2.dev_attr.attr, + &iio_dev_attr_poke_ev3.dev_attr.attr, + &iio_dev_attr_poke_ev4.dev_attr.attr, + &iio_dev_attr_poke_ev5.dev_attr.attr, + &iio_dev_attr_poke_ev6.dev_attr.attr, + &iio_dev_attr_poke_ev7.dev_attr.attr, + &iio_dev_attr_poke_ev8.dev_attr.attr, + &iio_dev_attr_poke_ev9.dev_attr.attr, + NULL, +}; + +static const struct attribute_group iio_evgen_group = { + .attrs = iio_evgen_attrs, +}; + +static const struct attribute_group *iio_evgen_groups[] = { + &iio_evgen_group, + NULL +}; + +static struct device iio_evgen_dev = { + .bus = &iio_bus_type, + .groups = iio_evgen_groups, + .release = &iio_evgen_release, +}; + +static __init int iio_dummy_evgen_init(void) +{ + int ret = iio_dummy_evgen_create(); + + if (ret < 0) + return ret; + device_initialize(&iio_evgen_dev); + dev_set_name(&iio_evgen_dev, "iio_evgen"); + return device_add(&iio_evgen_dev); +} +module_init(iio_dummy_evgen_init); + +static __exit void iio_dummy_evgen_exit(void) +{ + device_unregister(&iio_evgen_dev); +} +module_exit(iio_dummy_evgen_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dummy/iio_dummy_evgen.h b/drivers/iio/dummy/iio_dummy_evgen.h new file mode 100644 index 0000000..d044b94 --- /dev/null +++ b/drivers/iio/dummy/iio_dummy_evgen.h @@ -0,0 +1,13 @@ +#ifndef _IIO_DUMMY_EVGEN_H_ +#define _IIO_DUMMY_EVGEN_H_ + +struct iio_dummy_regs { + u32 reg_id; + u32 reg_data; +}; + +struct iio_dummy_regs *iio_dummy_evgen_get_regs(int irq); +int iio_dummy_evgen_get_irq(void); +void iio_dummy_evgen_release_irq(int irq); + +#endif /* _IIO_DUMMY_EVGEN_H_ */ diff --git b/drivers/iio/dummy/iio_simple_dummy.c b/drivers/iio/dummy/iio_simple_dummy.c new file mode 100644 index 0000000..43fe4ba --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.c @@ -0,0 +1,747 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * 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. + * + * A reference industrial I/O driver to illustrate the functionality available. + * + * There are numerous real drivers to illustrate the finer points. + * The purpose of this driver is to provide a driver with far more comments + * and explanatory notes than any 'real' driver would have. + * Anyone starting out writing an IIO driver should first make sure they + * understand all of this driver except those bits specifically marked + * as being present to allow us to 'fake' the presence of hardware. + */ +#include +#include +#include + +#include +#include +#include +#include +#include "iio_simple_dummy.h" + +/* + * A few elements needed to fake a bus for this driver + * Note instances parameter controls how many of these + * dummy devices are registered. + */ +static unsigned instances = 1; +module_param(instances, uint, 0); + +/* Pointer array used to fake bus elements */ +static struct iio_dev **iio_dummy_devs; + +/* Fake a name for the part number, usually obtained from the id table */ +static const char *iio_dummy_part_number = "iio_dummy_part_no"; + +/** + * struct iio_dummy_accel_calibscale - realworld to register mapping + * @val: first value in read_raw - here integer part. + * @val2: second value in read_raw etc - here micro part. + * @regval: register value - magic device specific numbers. + */ +struct iio_dummy_accel_calibscale { + int val; + int val2; + int regval; /* what would be written to hardware */ +}; + +static const struct iio_dummy_accel_calibscale dummy_scales[] = { + { 0, 100, 0x8 }, /* 0.000100 */ + { 0, 133, 0x7 }, /* 0.000133 */ + { 733, 13, 0x9 }, /* 733.000013 */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +/* + * simple event - triggered when value rises above + * a threshold + */ +static const struct iio_event_spec iio_dummy_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple step detect event - triggered when a step is detected + */ +static const struct iio_event_spec step_detect_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported running confidence + * value rises above a threshold value + */ +static const struct iio_event_spec iio_running_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; + +/* + * simple transition event - triggered when the reported walking confidence + * value falls under a threshold value + */ +static const struct iio_event_spec iio_walking_event = { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_ENABLE), +}; +#endif + +/* + * iio_dummy_channels - Description of available channels + * + * This array of structures tells the IIO core about what the device + * actually provides for a given channel. + */ +static const struct iio_chan_spec iio_dummy_channels[] = { + /* indexed ADC channel in_voltage0_raw etc */ + { + .type = IIO_VOLTAGE, + /* Channel has a numeric index of 0 */ + .indexed = 1, + .channel = 0, + /* What other information is available? */ + .info_mask_separate = + /* + * in_voltage0_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + BIT(IIO_CHAN_INFO_RAW) | + /* + * in_voltage0_offset + * Offset for userspace to apply prior to scale + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_OFFSET) | + /* + * in_voltage0_scale + * Multipler for userspace to apply post offset + * when converting to standard units (microvolts) + */ + BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + /* The ordering of elements in the buffer via an enum */ + .scan_index = DUMMY_INDEX_VOLTAGE_0, + .scan_type = { /* Description of storage in buffer */ + .sign = 'u', /* unsigned */ + .realbits = 13, /* 13 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_dummy_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + /* Differential ADC channel in_voltage1-voltage2_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + /* + * Indexing for differential channels uses channel + * for the positive part, channel2 for the negative. + */ + .indexed = 1, + .channel = 1, + .channel2 = 2, + /* + * in_voltage1-voltage2_raw + * Raw (unscaled no bias removal etc) measurement + * from the device. + */ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + /* + * in_voltage-voltage_scale + * Shared version of scale - shared by differential + * input channels of type IIO_VOLTAGE. + */ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + /* + * sampling_frequency + * The frequency in Hz at which the channels are sampled + */ + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_1M2, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 12, /* 12 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* Differential ADC channel in_voltage3-voltage4_raw etc*/ + { + .type = IIO_VOLTAGE, + .differential = 1, + .indexed = 1, + .channel = 3, + .channel2 = 4, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_DIFFVOLTAGE_3M4, + .scan_type = { + .sign = 's', + .realbits = 11, + .storagebits = 16, + .shift = 0, + }, + }, + /* + * 'modified' (i.e. axis specified) acceleration channel + * in_accel_z_raw + */ + { + .type = IIO_ACCEL, + .modified = 1, + /* Channel 2 is use for modifiers */ + .channel2 = IIO_MOD_X, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + /* + * Internal bias and gain correction values. Applied + * by the hardware or driver prior to userspace + * seeing the readings. Typically part of hardware + * calibration. + */ + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), + .info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .scan_index = DUMMY_INDEX_ACCELX, + .scan_type = { /* Description of storage in buffer */ + .sign = 's', /* signed */ + .realbits = 16, /* 16 bits */ + .storagebits = 16, /* 16 bits used for storage */ + .shift = 0, /* zero shift */ + }, + }, + /* + * Convenience macro for timestamps. 4 is the index in + * the buffer. + */ + IIO_CHAN_SOFT_TIMESTAMP(4), + /* DAC channel out_voltage0_raw */ + { + .type = IIO_VOLTAGE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = -1, /* No buffer support */ + .output = 1, + .indexed = 1, + .channel = 0, + }, + { + .type = IIO_STEPS, + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_CALIBHEIGHT), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &step_detect_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_RUNNING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_running_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, + { + .type = IIO_ACTIVITY, + .modified = 1, + .channel2 = IIO_MOD_WALKING, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .scan_index = -1, /* No buffer support */ +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .event_spec = &iio_walking_event, + .num_event_specs = 1, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ + }, +}; + +/** + * iio_dummy_read_raw() - data read function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be read + * @val: first element of returned value (typically INT) + * @val2: second element of returned value (typically MICRO) + * @mask: what we actually want to read as per the info_mask_* + * in iio_chan_spec. + */ +static int iio_dummy_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, + int *val2, + long mask) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret = -EINVAL; + + mutex_lock(&st->lock); + switch (mask) { + case IIO_CHAN_INFO_RAW: /* magic value - channel value read */ + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output) { + /* Set integer part to cached value */ + *val = st->dac_val; + ret = IIO_VAL_INT; + } else if (chan->differential) { + if (chan->channel == 1) + *val = st->differential_adc_val[0]; + else + *val = st->differential_adc_val[1]; + ret = IIO_VAL_INT; + } else { + *val = st->single_ended_adc_val; + ret = IIO_VAL_INT; + } + break; + case IIO_ACCEL: + *val = st->accel_val; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps; + ret = IIO_VAL_INT; + break; + case IIO_ACTIVITY: + switch (chan->channel2) { + case IIO_MOD_RUNNING: + *val = st->activity_running; + ret = IIO_VAL_INT; + break; + case IIO_MOD_WALKING: + *val = st->activity_walking; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_OFFSET: + /* only single ended adc -> 7 */ + *val = 7; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_VOLTAGE: + switch (chan->differential) { + case 0: + /* only single ended adc -> 0.001333 */ + *val = 0; + *val2 = 1333; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case 1: + /* all differential adc -> 0.000001344 */ + *val = 0; + *val2 = 1344; + ret = IIO_VAL_INT_PLUS_NANO; + } + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBBIAS: + /* only the acceleration axis - read from cache */ + *val = st->accel_calibbias; + ret = IIO_VAL_INT; + break; + case IIO_CHAN_INFO_CALIBSCALE: + *val = st->accel_calibscale->val; + *val2 = st->accel_calibscale->val2; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + case IIO_CHAN_INFO_SAMP_FREQ: + *val = 3; + *val2 = 33; + ret = IIO_VAL_INT_PLUS_NANO; + break; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + *val = st->steps_enabled; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + *val = st->height; + ret = IIO_VAL_INT; + break; + default: + break; + } + break; + + default: + break; + } + mutex_unlock(&st->lock); + return ret; +} + +/** + * iio_dummy_write_raw() - data write function. + * @indio_dev: the struct iio_dev associated with this device instance + * @chan: the channel whose data is to be written + * @val: first element of value to set (typically INT) + * @val2: second element of value to set (typically MICRO) + * @mask: what we actually want to write as per the info_mask_* + * in iio_chan_spec. + * + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt + * in struct iio_info is provided by the driver. + */ +static int iio_dummy_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, + int val2, + long mask) +{ + int i; + int ret = 0; + struct iio_dummy_state *st = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + if (chan->output == 0) + return -EINVAL; + + /* Locking not required as writing single value */ + mutex_lock(&st->lock); + st->dac_val = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PROCESSED: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps = val; + mutex_unlock(&st->lock); + return 0; + case IIO_ACTIVITY: + if (val < 0) + val = 0; + if (val > 100) + val = 100; + switch (chan->channel2) { + case IIO_MOD_RUNNING: + st->activity_running = val; + return 0; + case IIO_MOD_WALKING: + st->activity_walking = val; + return 0; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBSCALE: + mutex_lock(&st->lock); + /* Compare against table - hard matching here */ + for (i = 0; i < ARRAY_SIZE(dummy_scales); i++) + if (val == dummy_scales[i].val && + val2 == dummy_scales[i].val2) + break; + if (i == ARRAY_SIZE(dummy_scales)) + ret = -EINVAL; + else + st->accel_calibscale = &dummy_scales[i]; + mutex_unlock(&st->lock); + return ret; + case IIO_CHAN_INFO_CALIBBIAS: + mutex_lock(&st->lock); + st->accel_calibbias = val; + mutex_unlock(&st->lock); + return 0; + case IIO_CHAN_INFO_ENABLE: + switch (chan->type) { + case IIO_STEPS: + mutex_lock(&st->lock); + st->steps_enabled = val; + mutex_unlock(&st->lock); + return 0; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_CALIBHEIGHT: + switch (chan->type) { + case IIO_STEPS: + st->height = val; + return 0; + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +/* + * Device type specific information. + */ +static const struct iio_info iio_dummy_info = { + .driver_module = THIS_MODULE, + .read_raw = &iio_dummy_read_raw, + .write_raw = &iio_dummy_write_raw, +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + .read_event_config = &iio_simple_dummy_read_event_config, + .write_event_config = &iio_simple_dummy_write_event_config, + .read_event_value = &iio_simple_dummy_read_event_value, + .write_event_value = &iio_simple_dummy_write_event_value, +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +/** + * iio_dummy_init_device() - device instance specific init + * @indio_dev: the iio device structure + * + * Most drivers have one of these to set up default values, + * reset the device to known state etc. + */ +static int iio_dummy_init_device(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->dac_val = 0; + st->single_ended_adc_val = 73; + st->differential_adc_val[0] = 33; + st->differential_adc_val[1] = -34; + st->accel_val = 34; + st->accel_calibbias = -7; + st->accel_calibscale = &dummy_scales[0]; + st->steps = 47; + st->activity_running = 98; + st->activity_walking = 4; + + return 0; +} + +/** + * iio_dummy_probe() - device instance probe + * @index: an id number for this instance. + * + * Arguments are bus type specific. + * I2C: iio_dummy_probe(struct i2c_client *client, + * const struct i2c_device_id *id) + * SPI: iio_dummy_probe(struct spi_device *spi) + */ +static int iio_dummy_probe(int index) +{ + int ret; + struct iio_dev *indio_dev; + struct iio_dummy_state *st; + + /* + * Allocate an IIO device. + * + * This structure contains all generic state + * information about the device instance. + * It also has a region (accessed by iio_priv() + * for chip specific state information. + */ + indio_dev = iio_device_alloc(sizeof(*st)); + if (!indio_dev) { + ret = -ENOMEM; + goto error_ret; + } + + st = iio_priv(indio_dev); + mutex_init(&st->lock); + + iio_dummy_init_device(indio_dev); + /* + * With hardware: Set the parent device. + * indio_dev->dev.parent = &spi->dev; + * indio_dev->dev.parent = &client->dev; + */ + + /* + * Make the iio_dev struct available to remove function. + * Bus equivalents + * i2c_set_clientdata(client, indio_dev); + * spi_set_drvdata(spi, indio_dev); + */ + iio_dummy_devs[index] = indio_dev; + + /* + * Set the device name. + * + * This is typically a part number and obtained from the module + * id table. + * e.g. for i2c and spi: + * indio_dev->name = id->name; + * indio_dev->name = spi_get_device_id(spi)->name; + */ + indio_dev->name = iio_dummy_part_number; + + /* Provide description of available channels */ + indio_dev->channels = iio_dummy_channels; + indio_dev->num_channels = ARRAY_SIZE(iio_dummy_channels); + + /* + * Provide device type specific interface functions and + * constant data. + */ + indio_dev->info = &iio_dummy_info; + + /* Specify that device provides sysfs type interfaces */ + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_simple_dummy_events_register(indio_dev); + if (ret < 0) + goto error_free_device; + + ret = iio_simple_dummy_configure_buffer(indio_dev); + if (ret < 0) + goto error_unregister_events; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto error_unconfigure_buffer; + + return 0; +error_unconfigure_buffer: + iio_simple_dummy_unconfigure_buffer(indio_dev); +error_unregister_events: + iio_simple_dummy_events_unregister(indio_dev); +error_free_device: + iio_device_free(indio_dev); +error_ret: + return ret; +} + +/** + * iio_dummy_remove() - device instance removal function + * @index: device index. + * + * Parameters follow those of iio_dummy_probe for buses. + */ +static void iio_dummy_remove(int index) +{ + /* + * Get a pointer to the device instance iio_dev structure + * from the bus subsystem. E.g. + * struct iio_dev *indio_dev = i2c_get_clientdata(client); + * struct iio_dev *indio_dev = spi_get_drvdata(spi); + */ + struct iio_dev *indio_dev = iio_dummy_devs[index]; + + /* Unregister the device */ + iio_device_unregister(indio_dev); + + /* Device specific code to power down etc */ + + /* Buffered capture related cleanup */ + iio_simple_dummy_unconfigure_buffer(indio_dev); + + iio_simple_dummy_events_unregister(indio_dev); + + /* Free all structures */ + iio_device_free(indio_dev); +} + +/** + * iio_dummy_init() - device driver registration + * + * Varies depending on bus type of the device. As there is no device + * here, call probe directly. For information on device registration + * i2c: + * Documentation/i2c/writing-clients + * spi: + * Documentation/spi/spi-summary + */ +static __init int iio_dummy_init(void) +{ + int i, ret; + + if (instances > 10) { + instances = 1; + return -EINVAL; + } + + /* Fake a bus */ + iio_dummy_devs = kcalloc(instances, sizeof(*iio_dummy_devs), + GFP_KERNEL); + /* Here we have no actual device so call probe */ + for (i = 0; i < instances; i++) { + ret = iio_dummy_probe(i); + if (ret < 0) + goto error_remove_devs; + } + return 0; + +error_remove_devs: + while (i--) + iio_dummy_remove(i); + + kfree(iio_dummy_devs); + return ret; +} +module_init(iio_dummy_init); + +/** + * iio_dummy_exit() - device driver removal + * + * Varies depending on bus type of the device. + * As there is no device here, call remove directly. + */ +static __exit void iio_dummy_exit(void) +{ + int i; + + for (i = 0; i < instances; i++) + iio_dummy_remove(i); + kfree(iio_dummy_devs); +} +module_exit(iio_dummy_exit); + +MODULE_AUTHOR("Jonathan Cameron "); +MODULE_DESCRIPTION("IIO dummy driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/dummy/iio_simple_dummy.h b/drivers/iio/dummy/iio_simple_dummy.h new file mode 100644 index 0000000..b9069a1 --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy.h @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * 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. + * + * Join together the various functionality of iio_simple_dummy driver + */ + +#ifndef _IIO_SIMPLE_DUMMY_H_ +#define _IIO_SIMPLE_DUMMY_H_ +#include + +struct iio_dummy_accel_calibscale; +struct iio_dummy_regs; + +/** + * struct iio_dummy_state - device instance specific state. + * @dac_val: cache for dac value + * @single_ended_adc_val: cache for single ended adc value + * @differential_adc_val: cache for differential adc value + * @accel_val: cache for acceleration value + * @accel_calibbias: cache for acceleration calibbias + * @accel_calibscale: cache for acceleration calibscale + * @lock: lock to ensure state is consistent + * @event_irq: irq number for event line (faked) + * @event_val: cache for event threshold value + * @event_en: cache of whether event is enabled + */ +struct iio_dummy_state { + int dac_val; + int single_ended_adc_val; + int differential_adc_val[2]; + int accel_val; + int accel_calibbias; + int activity_running; + int activity_walking; + const struct iio_dummy_accel_calibscale *accel_calibscale; + struct mutex lock; + struct iio_dummy_regs *regs; + int steps_enabled; + int steps; + int height; +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + int event_irq; + int event_val; + bool event_en; + s64 event_timestamp; +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS */ +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_EVENTS + +struct iio_dev; + +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir); + +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state); + +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2); + +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2); + +int iio_simple_dummy_events_register(struct iio_dev *indio_dev); +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev); + +#else /* Stubs for when events are disabled at compile time */ + +static inline int +iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline void +iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ }; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_EVENTS*/ + +/** + * enum iio_simple_dummy_scan_elements - scan index enum + * @DUMMY_INDEX_VOLTAGE_0: the single ended voltage channel + * @DUMMY_INDEX_DIFFVOLTAGE_1M2: first differential channel + * @DUMMY_INDEX_DIFFVOLTAGE_3M4: second differential channel + * @DUMMY_INDEX_ACCELX: acceleration channel + * + * Enum provides convenient numbering for the scan index. + */ +enum iio_simple_dummy_scan_elements { + DUMMY_INDEX_VOLTAGE_0, + DUMMY_INDEX_DIFFVOLTAGE_1M2, + DUMMY_INDEX_DIFFVOLTAGE_3M4, + DUMMY_INDEX_ACCELX, +}; + +#ifdef CONFIG_IIO_SIMPLE_DUMMY_BUFFER +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev); +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev); +#else +static inline int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + return 0; +}; + +static inline +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{}; + +#endif /* CONFIG_IIO_SIMPLE_DUMMY_BUFFER */ +#endif /* _IIO_SIMPLE_DUMMY_H_ */ diff --git b/drivers/iio/dummy/iio_simple_dummy_buffer.c b/drivers/iio/dummy/iio_simple_dummy_buffer.c new file mode 100644 index 0000000..cf44a6f --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_buffer.c @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * 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. + * + * Buffer handling elements of industrial I/O reference driver. + * Uses the kfifo buffer. + * + * To test without hardware use the sysfs trigger. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "iio_simple_dummy.h" + +/* Some fake data */ + +static const s16 fakedata[] = { + [DUMMY_INDEX_VOLTAGE_0] = 7, + [DUMMY_INDEX_DIFFVOLTAGE_1M2] = -33, + [DUMMY_INDEX_DIFFVOLTAGE_3M4] = -2, + [DUMMY_INDEX_ACCELX] = 344, +}; + +/** + * iio_simple_dummy_trigger_h() - the trigger handler function + * @irq: the interrupt number + * @p: private data - always a pointer to the poll func. + * + * This is the guts of buffered capture. On a trigger event occurring, + * if the pollfunc is attached then this handler is called as a threaded + * interrupt (and hence may sleep). It is responsible for grabbing data + * from the device and pushing it into the associated buffer. + */ +static irqreturn_t iio_simple_dummy_trigger_h(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + int len = 0; + u16 *data; + + data = kmalloc(indio_dev->scan_bytes, GFP_KERNEL); + if (!data) + goto done; + + if (!bitmap_empty(indio_dev->active_scan_mask, indio_dev->masklength)) { + /* + * Three common options here: + * hardware scans: certain combinations of channels make + * up a fast read. The capture will consist of all of them. + * Hence we just call the grab data function and fill the + * buffer without processing. + * software scans: can be considered to be random access + * so efficient reading is just a case of minimal bus + * transactions. + * software culled hardware scans: + * occasionally a driver may process the nearest hardware + * scan to avoid storing elements that are not desired. This + * is the fiddliest option by far. + * Here let's pretend we have random access. And the values are + * in the constant table fakedata. + */ + int i, j; + + for (i = 0, j = 0; + i < bitmap_weight(indio_dev->active_scan_mask, + indio_dev->masklength); + i++, j++) { + j = find_next_bit(indio_dev->active_scan_mask, + indio_dev->masklength, j); + /* random access read from the 'device' */ + data[i] = fakedata[j]; + len += 2; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, data, iio_get_time_ns()); + + kfree(data); + +done: + /* + * Tell the core we are done with this trigger and ready for the + * next one. + */ + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_buffer_setup_ops iio_simple_dummy_buffer_setup_ops = { + /* + * iio_triggered_buffer_postenable: + * Generic function that simply attaches the pollfunc to the trigger. + * Replace this to mess with hardware state before we attach the + * trigger. + */ + .postenable = &iio_triggered_buffer_postenable, + /* + * iio_triggered_buffer_predisable: + * Generic function that simple detaches the pollfunc from the trigger. + * Replace this to put hardware state back again after the trigger is + * detached but before userspace knows we have disabled the ring. + */ + .predisable = &iio_triggered_buffer_predisable, +}; + +int iio_simple_dummy_configure_buffer(struct iio_dev *indio_dev) +{ + int ret; + struct iio_buffer *buffer; + + /* Allocate a buffer to use - here a kfifo */ + buffer = iio_kfifo_allocate(); + if (!buffer) { + ret = -ENOMEM; + goto error_ret; + } + + iio_device_attach_buffer(indio_dev, buffer); + + /* Enable timestamps by default */ + buffer->scan_timestamp = true; + + /* + * Tell the core what device type specific functions should + * be run on either side of buffer capture enable / disable. + */ + indio_dev->setup_ops = &iio_simple_dummy_buffer_setup_ops; + + /* + * Configure a polling function. + * When a trigger event with this polling function connected + * occurs, this function is run. Typically this grabs data + * from the device. + * + * NULL for the bottom half. This is normally implemented only if we + * either want to ping a capture now pin (no sleeping) or grab + * a timestamp as close as possible to a data ready trigger firing. + * + * IRQF_ONESHOT ensures irqs are masked such that only one instance + * of the handler can run at a time. + * + * "iio_simple_dummy_consumer%d" formatting string for the irq 'name' + * as seen under /proc/interrupts. Remaining parameters as per printk. + */ + indio_dev->pollfunc = iio_alloc_pollfunc(NULL, + &iio_simple_dummy_trigger_h, + IRQF_ONESHOT, + indio_dev, + "iio_simple_dummy_consumer%d", + indio_dev->id); + + if (!indio_dev->pollfunc) { + ret = -ENOMEM; + goto error_free_buffer; + } + + /* + * Notify the core that this device is capable of buffered capture + * driven by a trigger. + */ + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; + +error_free_buffer: + iio_kfifo_free(indio_dev->buffer); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_unconfigure_buffer() - release buffer resources + * @indo_dev: device instance state + */ +void iio_simple_dummy_unconfigure_buffer(struct iio_dev *indio_dev) +{ + iio_dealloc_pollfunc(indio_dev->pollfunc); + iio_kfifo_free(indio_dev->buffer); +} diff --git b/drivers/iio/dummy/iio_simple_dummy_events.c b/drivers/iio/dummy/iio_simple_dummy_events.c new file mode 100644 index 0000000..6eb600f --- /dev/null +++ b/drivers/iio/dummy/iio_simple_dummy_events.c @@ -0,0 +1,276 @@ +/** + * Copyright (c) 2011 Jonathan Cameron + * + * 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. + * + * Event handling elements of industrial I/O reference driver. + */ +#include +#include +#include +#include + +#include +#include +#include +#include "iio_simple_dummy.h" + +/* Evgen 'fakes' interrupt events for this example */ +#include "iio_dummy_evgen.h" + +/** + * iio_simple_dummy_read_event_config() - is event enabled? + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being queried + * @type: type of the event whose state is being queried + * @dir: direction of the vent whose state is being queried + * + * This function would normally query the relevant registers or a cache to + * discover if the event generation is enabled on the device. + */ +int iio_simple_dummy_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + return st->event_en; +} + +/** + * iio_simple_dummy_write_event_config() - set whether event is enabled + * @indio_dev: the device instance data + * @chan: channel for the event whose state is being set + * @type: type of the event whose state is being set + * @dir: direction of the vent whose state is being set + * @state: whether to enable or disable the device. + * + * This function would normally set the relevant registers on the devices + * so that it generates the specified event. Here it just sets up a cached + * value. + */ +int iio_simple_dummy_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + /* + * Deliberately over the top code splitting to illustrate + * how this is done when multiple events exist. + */ + switch (chan->type) { + case IIO_VOLTAGE: + switch (type) { + case IIO_EV_TYPE_THRESH: + if (dir == IIO_EV_DIR_RISING) + st->event_en = state; + else + return -EINVAL; + default: + return -EINVAL; + } + break; + case IIO_ACTIVITY: + switch (type) { + case IIO_EV_TYPE_THRESH: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + case IIO_STEPS: + switch (type) { + case IIO_EV_TYPE_CHANGE: + st->event_en = state; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * iio_simple_dummy_read_event_value() - get value associated with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being read + * @type: type of the event whose value is being read + * @dir: direction of the vent whose value is being read + * @info: info type of the event whose value is being read + * @val: value for the event code. + * + * Many devices provide a large set of events of which only a subset may + * be enabled at a time, with value registers whose meaning changes depending + * on the event enabled. This often means that the driver must cache the values + * associated with each possible events so that the right value is in place when + * the enabled event is changed. + */ +int iio_simple_dummy_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + *val = st->event_val; + + return IIO_VAL_INT; +} + +/** + * iio_simple_dummy_write_event_value() - set value associate with event + * @indio_dev: device instance specific data + * @chan: channel for the event whose value is being set + * @type: type of the event whose value is being set + * @dir: direction of the vent whose value is being set + * @info: info type of the event whose value is being set + * @val: the value to be set. + */ +int iio_simple_dummy_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_val = val; + + return 0; +} + +static irqreturn_t iio_simple_dummy_get_timestamp(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + st->event_timestamp = iio_get_time_ns(); + return IRQ_WAKE_THREAD; +} + +/** + * iio_simple_dummy_event_handler() - identify and pass on event + * @irq: irq of event line + * @private: pointer to device instance state. + * + * This handler is responsible for querying the device to find out what + * event occurred and for then pushing that event towards userspace. + * Here only one event occurs so we push that directly on with locally + * grabbed timestamp. + */ +static irqreturn_t iio_simple_dummy_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct iio_dummy_state *st = iio_priv(indio_dev); + + dev_dbg(&indio_dev->dev, "id %x event %x\n", + st->regs->reg_id, st->regs->reg_data); + + switch (st->regs->reg_data) { + case 0: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_VOLTAGE, 0, 0, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, 0, 0, 0), + st->event_timestamp); + break; + case 1: + if (st->activity_running > st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_RUNNING, + IIO_EV_DIR_RISING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + st->event_timestamp); + break; + case 2: + if (st->activity_walking < st->event_val) + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_ACTIVITY, 0, + IIO_MOD_WALKING, + IIO_EV_DIR_FALLING, + IIO_EV_TYPE_THRESH, + 0, 0, 0), + st->event_timestamp); + break; + case 3: + iio_push_event(indio_dev, + IIO_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, + IIO_EV_DIR_NONE, + IIO_EV_TYPE_CHANGE, 0, 0, 0), + st->event_timestamp); + break; + default: + break; + } + + return IRQ_HANDLED; +} + +/** + * iio_simple_dummy_events_register() - setup interrupt handling for events + * @indio_dev: device instance data + * + * This function requests the threaded interrupt to handle the events. + * Normally the irq is a hardware interrupt and the number comes + * from board configuration files. Here we get it from a companion + * module that fakes the interrupt for us. Note that module in + * no way forms part of this example. Just assume that events magically + * appear via the provided interrupt. + */ +int iio_simple_dummy_events_register(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + int ret; + + /* Fire up event source - normally not present */ + st->event_irq = iio_dummy_evgen_get_irq(); + if (st->event_irq < 0) { + ret = st->event_irq; + goto error_ret; + } + st->regs = iio_dummy_evgen_get_regs(st->event_irq); + + ret = request_threaded_irq(st->event_irq, + &iio_simple_dummy_get_timestamp, + &iio_simple_dummy_event_handler, + IRQF_ONESHOT, + "iio_simple_event", + indio_dev); + if (ret < 0) + goto error_free_evgen; + return 0; + +error_free_evgen: + iio_dummy_evgen_release_irq(st->event_irq); +error_ret: + return ret; +} + +/** + * iio_simple_dummy_events_unregister() - tidy up interrupt handling on remove + * @indio_dev: device instance data + */ +void iio_simple_dummy_events_unregister(struct iio_dev *indio_dev) +{ + struct iio_dummy_state *st = iio_priv(indio_dev); + + free_irq(st->event_irq, indio_dev); + /* Not part of normal driver */ + iio_dummy_evgen_release_irq(st->event_irq); +} diff --git a/drivers/iio/frequency/ad9523.c b/drivers/iio/frequency/ad9523.c index 44a30f2..99eba52 100644 --- a/drivers/iio/frequency/ad9523.c +++ b/drivers/iio/frequency/ad9523.c @@ -284,7 +284,7 @@ struct ad9523_state { } data[2] ____cacheline_aligned; }; -static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) +static int ad9523_read(struct iio_dev *indio_dev, unsigned int addr) { struct ad9523_state *st = iio_priv(indio_dev); int ret; @@ -318,7 +318,8 @@ static int ad9523_read(struct iio_dev *indio_dev, unsigned addr) return ret; }; -static int ad9523_write(struct iio_dev *indio_dev, unsigned addr, unsigned val) +static int ad9523_write(struct iio_dev *indio_dev, + unsigned int addr, unsigned int val) { struct ad9523_state *st = iio_priv(indio_dev); int ret; @@ -351,11 +352,11 @@ static int ad9523_io_update(struct iio_dev *indio_dev) } static int ad9523_vco_out_map(struct iio_dev *indio_dev, - unsigned ch, unsigned out) + unsigned int ch, unsigned int out) { struct ad9523_state *st = iio_priv(indio_dev); int ret; - unsigned mask; + unsigned int mask; switch (ch) { case 0 ... 3: @@ -405,7 +406,7 @@ static int ad9523_vco_out_map(struct iio_dev *indio_dev, } static int ad9523_set_clock_provider(struct iio_dev *indio_dev, - unsigned ch, unsigned long freq) + unsigned int ch, unsigned long freq) { struct ad9523_state *st = iio_priv(indio_dev); long tmp1, tmp2; @@ -619,7 +620,7 @@ static int ad9523_read_raw(struct iio_dev *indio_dev, long m) { struct ad9523_state *st = iio_priv(indio_dev); - unsigned code; + unsigned int code; int ret; mutex_lock(&indio_dev->mlock); @@ -655,7 +656,7 @@ static int ad9523_write_raw(struct iio_dev *indio_dev, long mask) { struct ad9523_state *st = iio_priv(indio_dev); - unsigned reg; + unsigned int reg; int ret, tmp, code; mutex_lock(&indio_dev->mlock); @@ -709,8 +710,8 @@ out: } static int ad9523_reg_access(struct iio_dev *indio_dev, - unsigned reg, unsigned writeval, - unsigned *readval) + unsigned int reg, unsigned int writeval, + unsigned int *readval) { int ret; diff --git a/drivers/iio/gyro/Kconfig b/drivers/iio/gyro/Kconfig index e816d29..205a844 100644 --- a/drivers/iio/gyro/Kconfig +++ b/drivers/iio/gyro/Kconfig @@ -93,7 +93,7 @@ config IIO_ST_GYRO_3AXIS select IIO_TRIGGERED_BUFFER if (IIO_BUFFER) help Say yes here to build support for STMicroelectronics gyroscopes: - L3G4200D, LSM330DL, L3GD20, LSM330DLC, L3G4IS, LSM330. + L3G4200D, LSM330DL, L3GD20, LSM330DLC, L3G4IS, LSM330, LSM9DS0. This driver can also be built as a module. If so, these modules will be created: diff --git a/drivers/iio/gyro/adis16136.c b/drivers/iio/gyro/adis16136.c index f8d1c22..b04faf9 100644 --- a/drivers/iio/gyro/adis16136.c +++ b/drivers/iio/gyro/adis16136.c @@ -435,7 +435,9 @@ static int adis16136_initial_setup(struct iio_dev *indio_dev) if (ret) return ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) + return -EINVAL; if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git a/drivers/iio/gyro/bmg160_core.c b/drivers/iio/gyro/bmg160_core.c index acb3b30..7ccc044 100644 --- a/drivers/iio/gyro/bmg160_core.c +++ b/drivers/iio/gyro/bmg160_core.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -31,7 +30,6 @@ #include "bmg160.h" #define BMG160_IRQ_NAME "bmg160_event" -#define BMG160_GPIO_NAME "gpio_int" #define BMG160_REG_CHIP_ID 0x00 #define BMG160_CHIP_ID_VAL 0x0F @@ -97,7 +95,6 @@ #define BMG160_AUTO_SUSPEND_DELAY_MS 2000 struct bmg160_data { - struct device *dev; struct regmap *regmap; struct iio_trigger *dready_trig; struct iio_trigger *motion_trig; @@ -116,6 +113,7 @@ enum bmg160_axis { AXIS_X, AXIS_Y, AXIS_Z, + AXIS_MAX, }; static const struct { @@ -138,11 +136,12 @@ static const struct { static int bmg160_set_mode(struct bmg160_data *data, u8 mode) { + struct device *dev = regmap_get_device(data->regmap); int ret; ret = regmap_write(data->regmap, BMG160_REG_PMU_LPW, mode); if (ret < 0) { - dev_err(data->dev, "Error writing reg_pmu_lpw\n"); + dev_err(dev, "Error writing reg_pmu_lpw\n"); return ret; } @@ -163,6 +162,7 @@ static int bmg160_convert_freq_to_bit(int val) static int bmg160_set_bw(struct bmg160_data *data, int val) { + struct device *dev = regmap_get_device(data->regmap); int ret; int bw_bits; @@ -172,7 +172,7 @@ static int bmg160_set_bw(struct bmg160_data *data, int val) ret = regmap_write(data->regmap, BMG160_REG_PMU_BW, bw_bits); if (ret < 0) { - dev_err(data->dev, "Error writing reg_pmu_bw\n"); + dev_err(dev, "Error writing reg_pmu_bw\n"); return ret; } @@ -183,18 +183,19 @@ static int bmg160_set_bw(struct bmg160_data *data, int val) static int bmg160_chip_init(struct bmg160_data *data) { + struct device *dev = regmap_get_device(data->regmap); int ret; unsigned int val; ret = regmap_read(data->regmap, BMG160_REG_CHIP_ID, &val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_chip_id\n"); + dev_err(dev, "Error reading reg_chip_id\n"); return ret; } - dev_dbg(data->dev, "Chip Id %x\n", val); + dev_dbg(dev, "Chip Id %x\n", val); if (val != BMG160_CHIP_ID_VAL) { - dev_err(data->dev, "invalid chip %x\n", val); + dev_err(dev, "invalid chip %x\n", val); return -ENODEV; } @@ -213,14 +214,14 @@ static int bmg160_chip_init(struct bmg160_data *data) /* Set Default Range */ ret = regmap_write(data->regmap, BMG160_REG_RANGE, BMG160_RANGE_500DPS); if (ret < 0) { - dev_err(data->dev, "Error writing reg_range\n"); + dev_err(dev, "Error writing reg_range\n"); return ret; } data->dps_range = BMG160_RANGE_500DPS; ret = regmap_read(data->regmap, BMG160_REG_SLOPE_THRES, &val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_slope_thres\n"); + dev_err(dev, "Error reading reg_slope_thres\n"); return ret; } data->slope_thres = val; @@ -229,7 +230,7 @@ static int bmg160_chip_init(struct bmg160_data *data) ret = regmap_update_bits(data->regmap, BMG160_REG_INT_EN_1, BMG160_INT1_BIT_OD, 0); if (ret < 0) { - dev_err(data->dev, "Error updating bits in reg_int_en_1\n"); + dev_err(dev, "Error updating bits in reg_int_en_1\n"); return ret; } @@ -237,7 +238,7 @@ static int bmg160_chip_init(struct bmg160_data *data) BMG160_INT_MODE_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, + dev_err(dev, "Error writing reg_motion_intr\n"); return ret; } @@ -248,20 +249,21 @@ static int bmg160_chip_init(struct bmg160_data *data) static int bmg160_set_power_state(struct bmg160_data *data, bool on) { #ifdef CONFIG_PM + struct device *dev = regmap_get_device(data->regmap); int ret; if (on) - ret = pm_runtime_get_sync(data->dev); + ret = pm_runtime_get_sync(dev); else { - pm_runtime_mark_last_busy(data->dev); - ret = pm_runtime_put_autosuspend(data->dev); + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); } if (ret < 0) { - dev_err(data->dev, - "Failed: bmg160_set_power_state for %d\n", on); + dev_err(dev, "Failed: bmg160_set_power_state for %d\n", on); + if (on) - pm_runtime_put_noidle(data->dev); + pm_runtime_put_noidle(dev); return ret; } @@ -273,6 +275,7 @@ static int bmg160_set_power_state(struct bmg160_data *data, bool on) static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, bool status) { + struct device *dev = regmap_get_device(data->regmap); int ret; /* Enable/Disable INT_MAP0 mapping */ @@ -280,7 +283,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, BMG160_INT_MAP_0_BIT_ANY, (status ? BMG160_INT_MAP_0_BIT_ANY : 0)); if (ret < 0) { - dev_err(data->dev, "Error updating bits reg_int_map0\n"); + dev_err(dev, "Error updating bits reg_int_map0\n"); return ret; } @@ -290,8 +293,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, ret = regmap_write(data->regmap, BMG160_REG_SLOPE_THRES, data->slope_thres); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_slope_thres\n"); + dev_err(dev, "Error writing reg_slope_thres\n"); return ret; } @@ -299,8 +301,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, BMG160_INT_MOTION_X | BMG160_INT_MOTION_Y | BMG160_INT_MOTION_Z); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_motion_intr\n"); + dev_err(dev, "Error writing reg_motion_intr\n"); return ret; } @@ -315,8 +316,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, BMG160_INT_MODE_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_rst_latch\n"); + dev_err(dev, "Error writing reg_rst_latch\n"); return ret; } } @@ -329,7 +329,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, } if (ret < 0) { - dev_err(data->dev, "Error writing reg_int_en0\n"); + dev_err(dev, "Error writing reg_int_en0\n"); return ret; } @@ -339,6 +339,7 @@ static int bmg160_setup_any_motion_interrupt(struct bmg160_data *data, static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, bool status) { + struct device *dev = regmap_get_device(data->regmap); int ret; /* Enable/Disable INT_MAP1 mapping */ @@ -346,7 +347,7 @@ static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, BMG160_INT_MAP_1_BIT_NEW_DATA, (status ? BMG160_INT_MAP_1_BIT_NEW_DATA : 0)); if (ret < 0) { - dev_err(data->dev, "Error updating bits in reg_int_map1\n"); + dev_err(dev, "Error updating bits in reg_int_map1\n"); return ret; } @@ -355,9 +356,8 @@ static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, BMG160_INT_MODE_NON_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_rst_latch\n"); - return ret; + dev_err(dev, "Error writing reg_rst_latch\n"); + return ret; } ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, @@ -369,16 +369,15 @@ static int bmg160_setup_new_data_interrupt(struct bmg160_data *data, BMG160_INT_MODE_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_rst_latch\n"); - return ret; + dev_err(dev, "Error writing reg_rst_latch\n"); + return ret; } ret = regmap_write(data->regmap, BMG160_REG_INT_EN_0, 0); } if (ret < 0) { - dev_err(data->dev, "Error writing reg_int_en0\n"); + dev_err(dev, "Error writing reg_int_en0\n"); return ret; } @@ -401,6 +400,7 @@ static int bmg160_get_bw(struct bmg160_data *data, int *val) static int bmg160_set_scale(struct bmg160_data *data, int val) { + struct device *dev = regmap_get_device(data->regmap); int ret, i; for (i = 0; i < ARRAY_SIZE(bmg160_scale_table); ++i) { @@ -408,8 +408,7 @@ static int bmg160_set_scale(struct bmg160_data *data, int val) ret = regmap_write(data->regmap, BMG160_REG_RANGE, bmg160_scale_table[i].dps_range); if (ret < 0) { - dev_err(data->dev, - "Error writing reg_range\n"); + dev_err(dev, "Error writing reg_range\n"); return ret; } data->dps_range = bmg160_scale_table[i].dps_range; @@ -422,6 +421,7 @@ static int bmg160_set_scale(struct bmg160_data *data, int val) static int bmg160_get_temp(struct bmg160_data *data, int *val) { + struct device *dev = regmap_get_device(data->regmap); int ret; unsigned int raw_val; @@ -434,7 +434,7 @@ static int bmg160_get_temp(struct bmg160_data *data, int *val) ret = regmap_read(data->regmap, BMG160_REG_TEMP, &raw_val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_temp\n"); + dev_err(dev, "Error reading reg_temp\n"); bmg160_set_power_state(data, false); mutex_unlock(&data->mutex); return ret; @@ -451,6 +451,7 @@ static int bmg160_get_temp(struct bmg160_data *data, int *val) static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val) { + struct device *dev = regmap_get_device(data->regmap); int ret; __le16 raw_val; @@ -464,7 +465,7 @@ static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val) ret = regmap_bulk_read(data->regmap, BMG160_AXIS_TO_REG(axis), &raw_val, sizeof(raw_val)); if (ret < 0) { - dev_err(data->dev, "Error reading axis %d\n", axis); + dev_err(dev, "Error reading axis %d\n", axis); bmg160_set_power_state(data, false); mutex_unlock(&data->mutex); return ret; @@ -764,26 +765,23 @@ static const struct iio_info bmg160_info = { .driver_module = THIS_MODULE, }; +static const unsigned long bmg160_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0}; + static irqreturn_t bmg160_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct bmg160_data *data = iio_priv(indio_dev); - int bit, ret, i = 0; - unsigned int val; + int ret; mutex_lock(&data->mutex); - for_each_set_bit(bit, indio_dev->active_scan_mask, - indio_dev->masklength) { - ret = regmap_bulk_read(data->regmap, BMG160_AXIS_TO_REG(bit), - &val, 2); - if (ret < 0) { - mutex_unlock(&data->mutex); - goto err; - } - data->buffer[i++] = val; - } + ret = regmap_bulk_read(data->regmap, BMG160_REG_XOUT_L, + data->buffer, AXIS_MAX * 2); mutex_unlock(&data->mutex); + if (ret < 0) + goto err; iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, pf->timestamp); @@ -797,6 +795,7 @@ static int bmg160_trig_try_reen(struct iio_trigger *trig) { struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); struct bmg160_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); int ret; /* new data interrupts don't need ack */ @@ -808,7 +807,7 @@ static int bmg160_trig_try_reen(struct iio_trigger *trig) BMG160_INT_MODE_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) { - dev_err(data->dev, "Error writing reg_rst_latch\n"); + dev_err(dev, "Error writing reg_rst_latch\n"); return ret; } @@ -868,13 +867,14 @@ static irqreturn_t bmg160_event_handler(int irq, void *private) { struct iio_dev *indio_dev = private; struct bmg160_data *data = iio_priv(indio_dev); + struct device *dev = regmap_get_device(data->regmap); int ret; int dir; unsigned int val; ret = regmap_read(data->regmap, BMG160_REG_INT_STATUS_2, &val); if (ret < 0) { - dev_err(data->dev, "Error reading reg_int_status2\n"); + dev_err(dev, "Error reading reg_int_status2\n"); goto ack_intr_status; } @@ -911,8 +911,7 @@ ack_intr_status: BMG160_INT_MODE_LATCH_INT | BMG160_INT_MODE_LATCH_RESET); if (ret < 0) - dev_err(data->dev, - "Error writing reg_rst_latch\n"); + dev_err(dev, "Error writing reg_rst_latch\n"); } return IRQ_HANDLED; @@ -956,29 +955,6 @@ static const struct iio_buffer_setup_ops bmg160_buffer_setup_ops = { .postdisable = bmg160_buffer_postdisable, }; -static int bmg160_gpio_probe(struct bmg160_data *data) - -{ - struct device *dev; - struct gpio_desc *gpio; - - dev = data->dev; - - /* data ready gpio interrupt pin */ - gpio = devm_gpiod_get_index(dev, BMG160_GPIO_NAME, 0, GPIOD_IN); - if (IS_ERR(gpio)) { - dev_err(dev, "acpi gpio get index failed\n"); - return PTR_ERR(gpio); - } - - data->irq = gpiod_to_irq(gpio); - - dev_dbg(dev, "GPIO resource, no:%d irq:%d\n", desc_to_gpio(gpio), - data->irq); - - return 0; -} - static const char *bmg160_match_acpi_device(struct device *dev) { const struct acpi_device_id *id; @@ -1003,7 +979,6 @@ int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, data = iio_priv(indio_dev); dev_set_drvdata(dev, indio_dev); - data->dev = dev; data->irq = irq; data->regmap = regmap; @@ -1020,12 +995,10 @@ int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, indio_dev->channels = bmg160_channels; indio_dev->num_channels = ARRAY_SIZE(bmg160_channels); indio_dev->name = name; + indio_dev->available_scan_masks = bmg160_accel_scan_masks; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &bmg160_info; - if (data->irq <= 0) - bmg160_gpio_probe(data); - if (data->irq > 0) { ret = devm_request_threaded_irq(dev, data->irq, @@ -1078,25 +1051,23 @@ int bmg160_core_probe(struct device *dev, struct regmap *regmap, int irq, goto err_trigger_unregister; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - ret = pm_runtime_set_active(dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; pm_runtime_enable(dev); pm_runtime_set_autosuspend_delay(dev, BMG160_AUTO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: iio_triggered_buffer_cleanup(indio_dev); err_trigger_unregister: @@ -1114,11 +1085,12 @@ void bmg160_core_remove(struct device *dev) struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmg160_data *data = iio_priv(indio_dev); + iio_device_unregister(indio_dev); + pm_runtime_disable(dev); pm_runtime_set_suspended(dev); pm_runtime_put_noidle(dev); - iio_device_unregister(indio_dev); iio_triggered_buffer_cleanup(indio_dev); if (data->dready_trig) { @@ -1169,7 +1141,7 @@ static int bmg160_runtime_suspend(struct device *dev) ret = bmg160_set_mode(data, BMG160_MODE_SUSPEND); if (ret < 0) { - dev_err(data->dev, "set mode failed\n"); + dev_err(dev, "set mode failed\n"); return -EAGAIN; } diff --git a/drivers/iio/gyro/st_gyro.h b/drivers/iio/gyro/st_gyro.h index 5353d63..a5c5c4e 100644 --- a/drivers/iio/gyro/st_gyro.h +++ b/drivers/iio/gyro/st_gyro.h @@ -21,6 +21,7 @@ #define L3GD20_GYRO_DEV_NAME "l3gd20" #define L3G4IS_GYRO_DEV_NAME "l3g4is_ui" #define LSM330_GYRO_DEV_NAME "lsm330_gyro" +#define LSM9DS0_GYRO_DEV_NAME "lsm9ds0_gyro" /** * struct st_sensors_platform_data - gyro platform data diff --git a/drivers/iio/gyro/st_gyro_buffer.c b/drivers/iio/gyro/st_gyro_buffer.c index d67b17b..a537704 100644 --- a/drivers/iio/gyro/st_gyro_buffer.c +++ b/drivers/iio/gyro/st_gyro_buffer.c @@ -91,7 +91,7 @@ static const struct iio_buffer_setup_ops st_gyro_buffer_setup_ops = { int st_gyro_allocate_ring(struct iio_dev *indio_dev) { - return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + return iio_triggered_buffer_setup(indio_dev, NULL, &st_sensors_trigger_handler, &st_gyro_buffer_setup_ops); } diff --git a/drivers/iio/gyro/st_gyro_core.c b/drivers/iio/gyro/st_gyro_core.c index 02eddce..a801295 100644 --- a/drivers/iio/gyro/st_gyro_core.c +++ b/drivers/iio/gyro/st_gyro_core.c @@ -185,6 +185,12 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = { .drdy_irq = { .addr = ST_GYRO_1_DRDY_IRQ_ADDR, .mask_int2 = ST_GYRO_1_DRDY_IRQ_INT2_MASK, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_GYRO_1_MULTIREAD_BIT, .bootime = 2, @@ -198,6 +204,7 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = { [2] = LSM330DLC_GYRO_DEV_NAME, [3] = L3G4IS_GYRO_DEV_NAME, [4] = LSM330_GYRO_DEV_NAME, + [5] = LSM9DS0_GYRO_DEV_NAME, }, .ch = (struct iio_chan_spec *)st_gyro_16bit_channels, .odr = { @@ -248,6 +255,12 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = { .drdy_irq = { .addr = ST_GYRO_2_DRDY_IRQ_ADDR, .mask_int2 = ST_GYRO_2_DRDY_IRQ_INT2_MASK, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_GYRO_2_MULTIREAD_BIT, .bootime = 2, @@ -307,6 +320,12 @@ static const struct st_sensor_settings st_gyro_sensors_settings[] = { .drdy_irq = { .addr = ST_GYRO_3_DRDY_IRQ_ADDR, .mask_int2 = ST_GYRO_3_DRDY_IRQ_INT2_MASK, + /* + * The sensor has IHL (active low) and open + * drain settings, but only for INT1 and not + * for the DRDY line on INT2. + */ + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_GYRO_3_MULTIREAD_BIT, .bootime = 2, @@ -390,6 +409,7 @@ static const struct iio_info gyro_info = { static const struct iio_trigger_ops st_gyro_trigger_ops = { .owner = THIS_MODULE, .set_trigger_state = ST_GYRO_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, }; #define ST_GYRO_TRIGGER_OPS (&st_gyro_trigger_ops) #else diff --git a/drivers/iio/gyro/st_gyro_i2c.c b/drivers/iio/gyro/st_gyro_i2c.c index 6848451..40056b8 100644 --- a/drivers/iio/gyro/st_gyro_i2c.c +++ b/drivers/iio/gyro/st_gyro_i2c.c @@ -48,6 +48,10 @@ static const struct of_device_id st_gyro_of_match[] = { .compatible = "st,lsm330-gyro", .data = LSM330_GYRO_DEV_NAME, }, + { + .compatible = "st,lsm9ds0-gyro", + .data = LSM9DS0_GYRO_DEV_NAME, + }, {}, }; MODULE_DEVICE_TABLE(of, st_gyro_of_match); @@ -93,6 +97,7 @@ static const struct i2c_device_id st_gyro_id_table[] = { { L3GD20_GYRO_DEV_NAME }, { L3G4IS_GYRO_DEV_NAME }, { LSM330_GYRO_DEV_NAME }, + { LSM9DS0_GYRO_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(i2c, st_gyro_id_table); diff --git a/drivers/iio/gyro/st_gyro_spi.c b/drivers/iio/gyro/st_gyro_spi.c index d2b7a5f..fbf2fae 100644 --- a/drivers/iio/gyro/st_gyro_spi.c +++ b/drivers/iio/gyro/st_gyro_spi.c @@ -54,6 +54,7 @@ static const struct spi_device_id st_gyro_id_table[] = { { L3GD20_GYRO_DEV_NAME }, { L3G4IS_GYRO_DEV_NAME }, { LSM330_GYRO_DEV_NAME }, + { LSM9DS0_GYRO_DEV_NAME }, {}, }; MODULE_DEVICE_TABLE(spi, st_gyro_id_table); diff --git b/drivers/iio/health/Kconfig b/drivers/iio/health/Kconfig new file mode 100644 index 0000000..c5f004a --- /dev/null +++ b/drivers/iio/health/Kconfig @@ -0,0 +1,51 @@ +# +# Health sensors +# +# When adding new entries keep the list in alphabetical order + +menu "Health Sensors" + +menu "Heart Rate Monitors" + +config AFE4403 + tristate "TI AFE4403 Heart Rate Monitor" + depends on SPI_MASTER + select REGMAP_SPI + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes to choose the Texas Instruments AFE4403 + heart rate monitor and low-cost pulse oximeter. + + To compile this driver as a module, choose M here: the + module will be called afe4403. + +config AFE4404 + tristate "TI AFE4404 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + Say yes to choose the Texas Instruments AFE4404 + heart rate monitor and low-cost pulse oximeter. + + To compile this driver as a module, choose M here: the + module will be called afe4404. + +config MAX30100 + tristate "MAX30100 heart rate and pulse oximeter sensor" + depends on I2C + select REGMAP_I2C + select IIO_BUFFER + select IIO_KFIFO_BUF + help + Say Y here to build I2C interface support for the Maxim + MAX30100 heart rate, and pulse oximeter sensor. + + To compile this driver as a module, choose M here: the + module will be called max30100. + +endmenu + +endmenu diff --git b/drivers/iio/health/Makefile b/drivers/iio/health/Makefile new file mode 100644 index 0000000..9955a2a --- /dev/null +++ b/drivers/iio/health/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for IIO Health sensors +# + +# When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_AFE4403) += afe4403.o +obj-$(CONFIG_AFE4404) += afe4404.o +obj-$(CONFIG_MAX30100) += max30100.o diff --git b/drivers/iio/health/afe4403.c b/drivers/iio/health/afe4403.c new file mode 100644 index 0000000..88e43f8 --- /dev/null +++ b/drivers/iio/health/afe4403.c @@ -0,0 +1,708 @@ +/* + * AFE4403 Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "afe440x.h" + +#define AFE4403_DRIVER_NAME "afe4403" + +/* AFE4403 Registers */ +#define AFE4403_TIAGAIN 0x20 +#define AFE4403_TIA_AMB_GAIN 0x21 + +/* AFE4403 GAIN register fields */ +#define AFE4403_TIAGAIN_RES_MASK GENMASK(2, 0) +#define AFE4403_TIAGAIN_RES_SHIFT 0 +#define AFE4403_TIAGAIN_CAP_MASK GENMASK(7, 3) +#define AFE4403_TIAGAIN_CAP_SHIFT 3 + +/* AFE4403 LEDCNTRL register fields */ +#define AFE440X_LEDCNTRL_LED1_MASK GENMASK(15, 8) +#define AFE440X_LEDCNTRL_LED1_SHIFT 8 +#define AFE440X_LEDCNTRL_LED2_MASK GENMASK(7, 0) +#define AFE440X_LEDCNTRL_LED2_SHIFT 0 +#define AFE440X_LEDCNTRL_LED_RANGE_MASK GENMASK(17, 16) +#define AFE440X_LEDCNTRL_LED_RANGE_SHIFT 16 + +/* AFE4403 CONTROL2 register fields */ +#define AFE440X_CONTROL2_PWR_DWN_TX BIT(2) +#define AFE440X_CONTROL2_EN_SLOW_DIAG BIT(8) +#define AFE440X_CONTROL2_DIAG_OUT_TRI BIT(10) +#define AFE440X_CONTROL2_TX_BRDG_MOD BIT(11) +#define AFE440X_CONTROL2_TX_REF_MASK GENMASK(18, 17) +#define AFE440X_CONTROL2_TX_REF_SHIFT 17 + +/* AFE4404 NULL fields */ +#define NULL_MASK 0 +#define NULL_SHIFT 0 + +/* AFE4403 LEDCNTRL values */ +#define AFE440X_LEDCNTRL_RANGE_TX_HALF 0x1 +#define AFE440X_LEDCNTRL_RANGE_TX_FULL 0x2 +#define AFE440X_LEDCNTRL_RANGE_TX_OFF 0x3 + +/* AFE4403 CONTROL2 values */ +#define AFE440X_CONTROL2_TX_REF_025 0x0 +#define AFE440X_CONTROL2_TX_REF_050 0x1 +#define AFE440X_CONTROL2_TX_REF_100 0x2 +#define AFE440X_CONTROL2_TX_REF_075 0x3 + +/* AFE4403 CONTROL3 values */ +#define AFE440X_CONTROL3_CLK_DIV_2 0x0 +#define AFE440X_CONTROL3_CLK_DIV_4 0x2 +#define AFE440X_CONTROL3_CLK_DIV_6 0x3 +#define AFE440X_CONTROL3_CLK_DIV_8 0x4 +#define AFE440X_CONTROL3_CLK_DIV_12 0x5 +#define AFE440X_CONTROL3_CLK_DIV_1 0x7 + +/* AFE4403 TIAGAIN_CAP values */ +#define AFE4403_TIAGAIN_CAP_5_P 0x0 +#define AFE4403_TIAGAIN_CAP_10_P 0x1 +#define AFE4403_TIAGAIN_CAP_20_P 0x2 +#define AFE4403_TIAGAIN_CAP_30_P 0x3 +#define AFE4403_TIAGAIN_CAP_55_P 0x8 +#define AFE4403_TIAGAIN_CAP_155_P 0x10 + +/* AFE4403 TIAGAIN_RES values */ +#define AFE4403_TIAGAIN_RES_500_K 0x0 +#define AFE4403_TIAGAIN_RES_250_K 0x1 +#define AFE4403_TIAGAIN_RES_100_K 0x2 +#define AFE4403_TIAGAIN_RES_50_K 0x3 +#define AFE4403_TIAGAIN_RES_25_K 0x4 +#define AFE4403_TIAGAIN_RES_10_K 0x5 +#define AFE4403_TIAGAIN_RES_1_M 0x6 +#define AFE4403_TIAGAIN_RES_NONE 0x7 + +/** + * struct afe4403_data + * @dev - Device structure + * @spi - SPI device handle + * @regmap - Register map of the device + * @regulator - Pointer to the regulator for the IC + * @trig - IIO trigger for this device + * @irq - ADC_RDY line interrupt number + */ +struct afe4403_data { + struct device *dev; + struct spi_device *spi; + struct regmap *regmap; + struct regulator *regulator; + struct iio_trigger *trig; + int irq; +}; + +enum afe4403_chan_id { + LED1, + ALED1, + LED2, + ALED2, + LED1_ALED1, + LED2_ALED2, + ILED1, + ILED2, +}; + +static const struct afe440x_reg_info afe4403_reg_info[] = { + [LED1] = AFE440X_REG_INFO(AFE440X_LED1VAL, 0, NULL), + [ALED1] = AFE440X_REG_INFO(AFE440X_ALED1VAL, 0, NULL), + [LED2] = AFE440X_REG_INFO(AFE440X_LED2VAL, 0, NULL), + [ALED2] = AFE440X_REG_INFO(AFE440X_ALED2VAL, 0, NULL), + [LED1_ALED1] = AFE440X_REG_INFO(AFE440X_LED1_ALED1VAL, 0, NULL), + [LED2_ALED2] = AFE440X_REG_INFO(AFE440X_LED2_ALED2VAL, 0, NULL), + [ILED1] = AFE440X_REG_INFO(AFE440X_LEDCNTRL, 0, AFE440X_LEDCNTRL_LED1), + [ILED2] = AFE440X_REG_INFO(AFE440X_LEDCNTRL, 0, AFE440X_LEDCNTRL_LED2), +}; + +static const struct iio_chan_spec afe4403_channels[] = { + /* ADC values */ + AFE440X_INTENSITY_CHAN(LED1, "led1", 0), + AFE440X_INTENSITY_CHAN(ALED1, "led1_ambient", 0), + AFE440X_INTENSITY_CHAN(LED2, "led2", 0), + AFE440X_INTENSITY_CHAN(ALED2, "led2_ambient", 0), + AFE440X_INTENSITY_CHAN(LED1_ALED1, "led1-led1_ambient", 0), + AFE440X_INTENSITY_CHAN(LED2_ALED2, "led2-led2_ambient", 0), + /* LED current */ + AFE440X_CURRENT_CHAN(ILED1, "led1"), + AFE440X_CURRENT_CHAN(ILED2, "led2"), +}; + +static const struct afe440x_val_table afe4403_res_table[] = { + { 500000 }, { 250000 }, { 100000 }, { 50000 }, + { 25000 }, { 10000 }, { 1000000 }, { 0 }, +}; +AFE440X_TABLE_ATTR(tia_resistance_available, afe4403_res_table); + +static const struct afe440x_val_table afe4403_cap_table[] = { + { 0, 5000 }, { 0, 10000 }, { 0, 20000 }, { 0, 25000 }, + { 0, 30000 }, { 0, 35000 }, { 0, 45000 }, { 0, 50000 }, + { 0, 55000 }, { 0, 60000 }, { 0, 70000 }, { 0, 75000 }, + { 0, 80000 }, { 0, 85000 }, { 0, 95000 }, { 0, 100000 }, + { 0, 155000 }, { 0, 160000 }, { 0, 170000 }, { 0, 175000 }, + { 0, 180000 }, { 0, 185000 }, { 0, 195000 }, { 0, 200000 }, + { 0, 205000 }, { 0, 210000 }, { 0, 220000 }, { 0, 225000 }, + { 0, 230000 }, { 0, 235000 }, { 0, 245000 }, { 0, 250000 }, +}; +AFE440X_TABLE_ATTR(tia_capacitance_available, afe4403_cap_table); + +static ssize_t afe440x_show_register(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + unsigned int reg_val, type; + int vals[2]; + int ret, val_len; + + ret = regmap_read(afe->regmap, afe440x_attr->reg, ®_val); + if (ret) + return ret; + + reg_val &= afe440x_attr->mask; + reg_val >>= afe440x_attr->shift; + + switch (afe440x_attr->type) { + case SIMPLE: + type = IIO_VAL_INT; + val_len = 1; + vals[0] = reg_val; + break; + case RESISTANCE: + case CAPACITANCE: + type = IIO_VAL_INT_PLUS_MICRO; + val_len = 2; + if (reg_val < afe440x_attr->table_size) { + vals[0] = afe440x_attr->val_table[reg_val].integer; + vals[1] = afe440x_attr->val_table[reg_val].fract; + break; + } + return -EINVAL; + default: + return -EINVAL; + } + + return iio_format_value(buf, type, val_len, vals); +} + +static ssize_t afe440x_store_register(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + int val, integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + switch (afe440x_attr->type) { + case SIMPLE: + val = integer; + break; + case RESISTANCE: + case CAPACITANCE: + for (val = 0; val < afe440x_attr->table_size; val++) + if (afe440x_attr->val_table[val].integer == integer && + afe440x_attr->val_table[val].fract == fract) + break; + if (val == afe440x_attr->table_size) + return -EINVAL; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(afe->regmap, afe440x_attr->reg, + afe440x_attr->mask, + (val << afe440x_attr->shift)); + if (ret) + return ret; + + return count; +} + +static AFE440X_ATTR(tia_separate_en, AFE4403_TIAGAIN, AFE440X_TIAGAIN_ENSEPGAIN, SIMPLE, NULL, 0); + +static AFE440X_ATTR(tia_resistance1, AFE4403_TIAGAIN, AFE4403_TIAGAIN_RES, RESISTANCE, afe4403_res_table, ARRAY_SIZE(afe4403_res_table)); +static AFE440X_ATTR(tia_capacitance1, AFE4403_TIAGAIN, AFE4403_TIAGAIN_CAP, CAPACITANCE, afe4403_cap_table, ARRAY_SIZE(afe4403_cap_table)); + +static AFE440X_ATTR(tia_resistance2, AFE4403_TIA_AMB_GAIN, AFE4403_TIAGAIN_RES, RESISTANCE, afe4403_res_table, ARRAY_SIZE(afe4403_res_table)); +static AFE440X_ATTR(tia_capacitance2, AFE4403_TIA_AMB_GAIN, AFE4403_TIAGAIN_RES, CAPACITANCE, afe4403_cap_table, ARRAY_SIZE(afe4403_cap_table)); + +static struct attribute *afe440x_attributes[] = { + &afe440x_attr_tia_separate_en.dev_attr.attr, + &afe440x_attr_tia_resistance1.dev_attr.attr, + &afe440x_attr_tia_capacitance1.dev_attr.attr, + &afe440x_attr_tia_resistance2.dev_attr.attr, + &afe440x_attr_tia_capacitance2.dev_attr.attr, + &dev_attr_tia_resistance_available.attr, + &dev_attr_tia_capacitance_available.attr, + NULL +}; + +static const struct attribute_group afe440x_attribute_group = { + .attrs = afe440x_attributes +}; + +static int afe4403_read(struct afe4403_data *afe, unsigned int reg, u32 *val) +{ + u8 tx[4] = {AFE440X_CONTROL0, 0x0, 0x0, AFE440X_CONTROL0_READ}; + u8 rx[3]; + int ret; + + /* Enable reading from the device */ + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + return ret; + + ret = spi_write_then_read(afe->spi, ®, 1, rx, 3); + if (ret) + return ret; + + *val = (rx[0] << 16) | + (rx[1] << 8) | + (rx[2]); + + /* Disable reading from the device */ + tx[3] = AFE440X_CONTROL0_WRITE; + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + return ret; + + return 0; +} + +static int afe4403_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct afe4403_data *afe = iio_priv(indio_dev); + const struct afe440x_reg_info reg_info = afe4403_reg_info[chan->address]; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = afe4403_read(afe, reg_info.reg, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ret = regmap_read(afe->regmap, reg_info.offreg, + val); + if (ret) + return ret; + *val &= reg_info.mask; + *val >>= reg_info.shift; + return IIO_VAL_INT; + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(afe->regmap, reg_info.reg, val); + if (ret) + return ret; + *val &= reg_info.mask; + *val >>= reg_info.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + + return -EINVAL; +} + +static int afe4403_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct afe4403_data *afe = iio_priv(indio_dev); + const struct afe440x_reg_info reg_info = afe4403_reg_info[chan->address]; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + return regmap_update_bits(afe->regmap, + reg_info.offreg, + reg_info.mask, + (val << reg_info.shift)); + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + return regmap_update_bits(afe->regmap, + reg_info.reg, + reg_info.mask, + (val << reg_info.shift)); + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info afe4403_iio_info = { + .attrs = &afe440x_attribute_group, + .read_raw = afe4403_read_raw, + .write_raw = afe4403_write_raw, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t afe4403_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct afe4403_data *afe = iio_priv(indio_dev); + int ret, bit, i = 0; + s32 buffer[8]; + u8 tx[4] = {AFE440X_CONTROL0, 0x0, 0x0, AFE440X_CONTROL0_READ}; + u8 rx[3]; + + /* Enable reading from the device */ + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + goto err; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = spi_write_then_read(afe->spi, + &afe4403_reg_info[bit].reg, 1, + rx, 3); + if (ret) + goto err; + + buffer[i++] = (rx[0] << 16) | + (rx[1] << 8) | + (rx[2]); + } + + /* Disable reading from the device */ + tx[3] = AFE440X_CONTROL0_WRITE; + ret = spi_write_then_read(afe->spi, tx, 4, NULL, 0); + if (ret) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops afe4403_trigger_ops = { + .owner = THIS_MODULE, +}; + +#define AFE4403_TIMING_PAIRS \ + { AFE440X_LED2STC, 0x000050 }, \ + { AFE440X_LED2ENDC, 0x0003e7 }, \ + { AFE440X_LED1LEDSTC, 0x0007d0 }, \ + { AFE440X_LED1LEDENDC, 0x000bb7 }, \ + { AFE440X_ALED2STC, 0x000438 }, \ + { AFE440X_ALED2ENDC, 0x0007cf }, \ + { AFE440X_LED1STC, 0x000820 }, \ + { AFE440X_LED1ENDC, 0x000bb7 }, \ + { AFE440X_LED2LEDSTC, 0x000000 }, \ + { AFE440X_LED2LEDENDC, 0x0003e7 }, \ + { AFE440X_ALED1STC, 0x000c08 }, \ + { AFE440X_ALED1ENDC, 0x000f9f }, \ + { AFE440X_LED2CONVST, 0x0003ef }, \ + { AFE440X_LED2CONVEND, 0x0007cf }, \ + { AFE440X_ALED2CONVST, 0x0007d7 }, \ + { AFE440X_ALED2CONVEND, 0x000bb7 }, \ + { AFE440X_LED1CONVST, 0x000bbf }, \ + { AFE440X_LED1CONVEND, 0x009c3f }, \ + { AFE440X_ALED1CONVST, 0x000fa7 }, \ + { AFE440X_ALED1CONVEND, 0x001387 }, \ + { AFE440X_ADCRSTSTCT0, 0x0003e8 }, \ + { AFE440X_ADCRSTENDCT0, 0x0003eb }, \ + { AFE440X_ADCRSTSTCT1, 0x0007d0 }, \ + { AFE440X_ADCRSTENDCT1, 0x0007d3 }, \ + { AFE440X_ADCRSTSTCT2, 0x000bb8 }, \ + { AFE440X_ADCRSTENDCT2, 0x000bbb }, \ + { AFE440X_ADCRSTSTCT3, 0x000fa0 }, \ + { AFE440X_ADCRSTENDCT3, 0x000fa3 }, \ + { AFE440X_PRPCOUNT, 0x009c3f }, \ + { AFE440X_PDNCYCLESTC, 0x001518 }, \ + { AFE440X_PDNCYCLEENDC, 0x00991f } + +static const struct reg_sequence afe4403_reg_sequences[] = { + AFE4403_TIMING_PAIRS, + { AFE440X_CONTROL1, AFE440X_CONTROL1_TIMEREN | 0x000007}, + { AFE4403_TIA_AMB_GAIN, AFE4403_TIAGAIN_RES_1_M }, + { AFE440X_LEDCNTRL, (0x14 << AFE440X_LEDCNTRL_LED1_SHIFT) | + (0x14 << AFE440X_LEDCNTRL_LED2_SHIFT) }, + { AFE440X_CONTROL2, AFE440X_CONTROL2_TX_REF_050 << + AFE440X_CONTROL2_TX_REF_SHIFT }, +}; + +static const struct regmap_range afe4403_yes_ranges[] = { + regmap_reg_range(AFE440X_LED2VAL, AFE440X_LED1_ALED1VAL), +}; + +static const struct regmap_access_table afe4403_volatile_table = { + .yes_ranges = afe4403_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(afe4403_yes_ranges), +}; + +static const struct regmap_config afe4403_regmap_config = { + .reg_bits = 8, + .val_bits = 24, + + .max_register = AFE440X_PDNCYCLEENDC, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &afe4403_volatile_table, +}; + +#ifdef CONFIG_OF +static const struct of_device_id afe4403_of_match[] = { + { .compatible = "ti,afe4403", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, afe4403_of_match); +#endif + +static int __maybe_unused afe4403_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static int __maybe_unused afe4403_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4403_data *afe = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_update_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE, 0); + if (ret) + return ret; + + return 0; +} + +static SIMPLE_DEV_PM_OPS(afe4403_pm_ops, afe4403_suspend, afe4403_resume); + +static int afe4403_probe(struct spi_device *spi) +{ + struct iio_dev *indio_dev; + struct afe4403_data *afe; + int ret; + + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*afe)); + if (!indio_dev) + return -ENOMEM; + + afe = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + + afe->dev = &spi->dev; + afe->spi = spi; + afe->irq = spi->irq; + + afe->regmap = devm_regmap_init_spi(spi, &afe4403_regmap_config); + if (IS_ERR(afe->regmap)) { + dev_err(afe->dev, "Unable to allocate register map\n"); + return PTR_ERR(afe->regmap); + } + + afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + if (IS_ERR(afe->regulator)) { + dev_err(afe->dev, "Unable to get regulator\n"); + return PTR_ERR(afe->regulator); + } + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(afe->dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_write(afe->regmap, AFE440X_CONTROL0, + AFE440X_CONTROL0_SW_RESET); + if (ret) { + dev_err(afe->dev, "Unable to reset device\n"); + goto err_disable_reg; + } + + ret = regmap_multi_reg_write(afe->regmap, afe4403_reg_sequences, + ARRAY_SIZE(afe4403_reg_sequences)); + if (ret) { + dev_err(afe->dev, "Unable to set register defaults\n"); + goto err_disable_reg; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = afe->dev; + indio_dev->channels = afe4403_channels; + indio_dev->num_channels = ARRAY_SIZE(afe4403_channels); + indio_dev->name = AFE4403_DRIVER_NAME; + indio_dev->info = &afe4403_iio_info; + + if (afe->irq > 0) { + afe->trig = devm_iio_trigger_alloc(afe->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!afe->trig) { + dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + ret = -ENOMEM; + goto err_disable_reg; + } + + iio_trigger_set_drvdata(afe->trig, indio_dev); + + afe->trig->ops = &afe4403_trigger_ops; + afe->trig->dev.parent = afe->dev; + + ret = iio_trigger_register(afe->trig); + if (ret) { + dev_err(afe->dev, "Unable to register IIO trigger\n"); + goto err_disable_reg; + } + + ret = devm_request_threaded_irq(afe->dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + NULL, IRQF_ONESHOT, + AFE4403_DRIVER_NAME, + afe->trig); + if (ret) { + dev_err(afe->dev, "Unable to request IRQ\n"); + goto err_trig; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + afe4403_trigger_handler, NULL); + if (ret) { + dev_err(afe->dev, "Unable to setup buffer\n"); + goto err_trig; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(afe->dev, "Unable to register IIO device\n"); + goto err_buff; + } + + return 0; + +err_buff: + iio_triggered_buffer_cleanup(indio_dev); +err_trig: + if (afe->irq > 0) + iio_trigger_unregister(afe->trig); +err_disable_reg: + regulator_disable(afe->regulator); + + return ret; +} + +static int afe4403_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct afe4403_data *afe = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + + iio_triggered_buffer_cleanup(indio_dev); + + if (afe->irq > 0) + iio_trigger_unregister(afe->trig); + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(afe->dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static const struct spi_device_id afe4403_ids[] = { + { "afe4403", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, afe4403_ids); + +static struct spi_driver afe4403_spi_driver = { + .driver = { + .name = AFE4403_DRIVER_NAME, + .of_match_table = of_match_ptr(afe4403_of_match), + .pm = &afe4403_pm_ops, + }, + .probe = afe4403_probe, + .remove = afe4403_remove, + .id_table = afe4403_ids, +}; +module_spi_driver(afe4403_spi_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("TI AFE4403 Heart Rate and Pulse Oximeter"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/health/afe4404.c b/drivers/iio/health/afe4404.c new file mode 100644 index 0000000..5096a46 --- /dev/null +++ b/drivers/iio/health/afe4404.c @@ -0,0 +1,679 @@ +/* + * AFE4404 Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "afe440x.h" + +#define AFE4404_DRIVER_NAME "afe4404" + +/* AFE4404 registers */ +#define AFE4404_TIA_GAIN_SEP 0x20 +#define AFE4404_TIA_GAIN 0x21 +#define AFE4404_PROG_TG_STC 0x34 +#define AFE4404_PROG_TG_ENDC 0x35 +#define AFE4404_LED3LEDSTC 0x36 +#define AFE4404_LED3LEDENDC 0x37 +#define AFE4404_CLKDIV_PRF 0x39 +#define AFE4404_OFFDAC 0x3a +#define AFE4404_DEC 0x3d +#define AFE4404_AVG_LED2_ALED2VAL 0x3f +#define AFE4404_AVG_LED1_ALED1VAL 0x40 + +/* AFE4404 GAIN register fields */ +#define AFE4404_TIA_GAIN_RES_MASK GENMASK(2, 0) +#define AFE4404_TIA_GAIN_RES_SHIFT 0 +#define AFE4404_TIA_GAIN_CAP_MASK GENMASK(5, 3) +#define AFE4404_TIA_GAIN_CAP_SHIFT 3 + +/* AFE4404 LEDCNTRL register fields */ +#define AFE4404_LEDCNTRL_ILED1_MASK GENMASK(5, 0) +#define AFE4404_LEDCNTRL_ILED1_SHIFT 0 +#define AFE4404_LEDCNTRL_ILED2_MASK GENMASK(11, 6) +#define AFE4404_LEDCNTRL_ILED2_SHIFT 6 +#define AFE4404_LEDCNTRL_ILED3_MASK GENMASK(17, 12) +#define AFE4404_LEDCNTRL_ILED3_SHIFT 12 + +/* AFE4404 CONTROL2 register fields */ +#define AFE440X_CONTROL2_ILED_2X_MASK BIT(17) +#define AFE440X_CONTROL2_ILED_2X_SHIFT 17 + +/* AFE4404 CONTROL3 register fields */ +#define AFE440X_CONTROL3_OSC_ENABLE BIT(9) + +/* AFE4404 OFFDAC register current fields */ +#define AFE4404_OFFDAC_CURR_LED1_MASK GENMASK(9, 5) +#define AFE4404_OFFDAC_CURR_LED1_SHIFT 5 +#define AFE4404_OFFDAC_CURR_LED2_MASK GENMASK(19, 15) +#define AFE4404_OFFDAC_CURR_LED2_SHIFT 15 +#define AFE4404_OFFDAC_CURR_LED3_MASK GENMASK(4, 0) +#define AFE4404_OFFDAC_CURR_LED3_SHIFT 0 +#define AFE4404_OFFDAC_CURR_ALED1_MASK GENMASK(14, 10) +#define AFE4404_OFFDAC_CURR_ALED1_SHIFT 10 +#define AFE4404_OFFDAC_CURR_ALED2_MASK GENMASK(4, 0) +#define AFE4404_OFFDAC_CURR_ALED2_SHIFT 0 + +/* AFE4404 NULL fields */ +#define NULL_MASK 0 +#define NULL_SHIFT 0 + +/* AFE4404 TIA_GAIN_CAP values */ +#define AFE4404_TIA_GAIN_CAP_5_P 0x0 +#define AFE4404_TIA_GAIN_CAP_2_5_P 0x1 +#define AFE4404_TIA_GAIN_CAP_10_P 0x2 +#define AFE4404_TIA_GAIN_CAP_7_5_P 0x3 +#define AFE4404_TIA_GAIN_CAP_20_P 0x4 +#define AFE4404_TIA_GAIN_CAP_17_5_P 0x5 +#define AFE4404_TIA_GAIN_CAP_25_P 0x6 +#define AFE4404_TIA_GAIN_CAP_22_5_P 0x7 + +/* AFE4404 TIA_GAIN_RES values */ +#define AFE4404_TIA_GAIN_RES_500_K 0x0 +#define AFE4404_TIA_GAIN_RES_250_K 0x1 +#define AFE4404_TIA_GAIN_RES_100_K 0x2 +#define AFE4404_TIA_GAIN_RES_50_K 0x3 +#define AFE4404_TIA_GAIN_RES_25_K 0x4 +#define AFE4404_TIA_GAIN_RES_10_K 0x5 +#define AFE4404_TIA_GAIN_RES_1_M 0x6 +#define AFE4404_TIA_GAIN_RES_2_M 0x7 + +/** + * struct afe4404_data + * @dev - Device structure + * @regmap - Register map of the device + * @regulator - Pointer to the regulator for the IC + * @trig - IIO trigger for this device + * @irq - ADC_RDY line interrupt number + */ +struct afe4404_data { + struct device *dev; + struct regmap *regmap; + struct regulator *regulator; + struct iio_trigger *trig; + int irq; +}; + +enum afe4404_chan_id { + LED1, + ALED1, + LED2, + ALED2, + LED3, + LED1_ALED1, + LED2_ALED2, + ILED1, + ILED2, + ILED3, +}; + +static const struct afe440x_reg_info afe4404_reg_info[] = { + [LED1] = AFE440X_REG_INFO(AFE440X_LED1VAL, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_LED1), + [ALED1] = AFE440X_REG_INFO(AFE440X_ALED1VAL, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_ALED1), + [LED2] = AFE440X_REG_INFO(AFE440X_LED2VAL, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_LED2), + [ALED2] = AFE440X_REG_INFO(AFE440X_ALED2VAL, AFE4404_OFFDAC, AFE4404_OFFDAC_CURR_ALED2), + [LED3] = AFE440X_REG_INFO(AFE440X_ALED2VAL, 0, NULL), + [LED1_ALED1] = AFE440X_REG_INFO(AFE440X_LED1_ALED1VAL, 0, NULL), + [LED2_ALED2] = AFE440X_REG_INFO(AFE440X_LED2_ALED2VAL, 0, NULL), + [ILED1] = AFE440X_REG_INFO(AFE440X_LEDCNTRL, 0, AFE4404_LEDCNTRL_ILED1), + [ILED2] = AFE440X_REG_INFO(AFE440X_LEDCNTRL, 0, AFE4404_LEDCNTRL_ILED2), + [ILED3] = AFE440X_REG_INFO(AFE440X_LEDCNTRL, 0, AFE4404_LEDCNTRL_ILED3), +}; + +static const struct iio_chan_spec afe4404_channels[] = { + /* ADC values */ + AFE440X_INTENSITY_CHAN(LED1, "led1", BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(ALED1, "led1_ambient", BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(LED2, "led2", BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(ALED2, "led2_ambient", BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(LED3, "led3", BIT(IIO_CHAN_INFO_OFFSET)), + AFE440X_INTENSITY_CHAN(LED1_ALED1, "led1-led1_ambient", 0), + AFE440X_INTENSITY_CHAN(LED2_ALED2, "led2-led2_ambient", 0), + /* LED current */ + AFE440X_CURRENT_CHAN(ILED1, "led1"), + AFE440X_CURRENT_CHAN(ILED2, "led2"), + AFE440X_CURRENT_CHAN(ILED3, "led3"), +}; + +static const struct afe440x_val_table afe4404_res_table[] = { + { .integer = 500000, .fract = 0 }, + { .integer = 250000, .fract = 0 }, + { .integer = 100000, .fract = 0 }, + { .integer = 50000, .fract = 0 }, + { .integer = 25000, .fract = 0 }, + { .integer = 10000, .fract = 0 }, + { .integer = 1000000, .fract = 0 }, + { .integer = 2000000, .fract = 0 }, +}; +AFE440X_TABLE_ATTR(tia_resistance_available, afe4404_res_table); + +static const struct afe440x_val_table afe4404_cap_table[] = { + { .integer = 0, .fract = 5000 }, + { .integer = 0, .fract = 2500 }, + { .integer = 0, .fract = 10000 }, + { .integer = 0, .fract = 7500 }, + { .integer = 0, .fract = 20000 }, + { .integer = 0, .fract = 17500 }, + { .integer = 0, .fract = 25000 }, + { .integer = 0, .fract = 22500 }, +}; +AFE440X_TABLE_ATTR(tia_capacitance_available, afe4404_cap_table); + +static ssize_t afe440x_show_register(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + unsigned int reg_val, type; + int vals[2]; + int ret, val_len; + + ret = regmap_read(afe->regmap, afe440x_attr->reg, ®_val); + if (ret) + return ret; + + reg_val &= afe440x_attr->mask; + reg_val >>= afe440x_attr->shift; + + switch (afe440x_attr->type) { + case SIMPLE: + type = IIO_VAL_INT; + val_len = 1; + vals[0] = reg_val; + break; + case RESISTANCE: + case CAPACITANCE: + type = IIO_VAL_INT_PLUS_MICRO; + val_len = 2; + if (reg_val < afe440x_attr->table_size) { + vals[0] = afe440x_attr->val_table[reg_val].integer; + vals[1] = afe440x_attr->val_table[reg_val].fract; + break; + } + return -EINVAL; + default: + return -EINVAL; + } + + return iio_format_value(buf, type, val_len, vals); +} + +static ssize_t afe440x_store_register(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + struct afe440x_attr *afe440x_attr = to_afe440x_attr(attr); + int val, integer, fract, ret; + + ret = iio_str_to_fixpoint(buf, 100000, &integer, &fract); + if (ret) + return ret; + + switch (afe440x_attr->type) { + case SIMPLE: + val = integer; + break; + case RESISTANCE: + case CAPACITANCE: + for (val = 0; val < afe440x_attr->table_size; val++) + if (afe440x_attr->val_table[val].integer == integer && + afe440x_attr->val_table[val].fract == fract) + break; + if (val == afe440x_attr->table_size) + return -EINVAL; + break; + default: + return -EINVAL; + } + + ret = regmap_update_bits(afe->regmap, afe440x_attr->reg, + afe440x_attr->mask, + (val << afe440x_attr->shift)); + if (ret) + return ret; + + return count; +} + +static AFE440X_ATTR(tia_separate_en, AFE4404_TIA_GAIN_SEP, AFE440X_TIAGAIN_ENSEPGAIN, SIMPLE, NULL, 0); + +static AFE440X_ATTR(tia_resistance1, AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_RES, RESISTANCE, afe4404_res_table, ARRAY_SIZE(afe4404_res_table)); +static AFE440X_ATTR(tia_capacitance1, AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_CAP, CAPACITANCE, afe4404_cap_table, ARRAY_SIZE(afe4404_cap_table)); + +static AFE440X_ATTR(tia_resistance2, AFE4404_TIA_GAIN_SEP, AFE4404_TIA_GAIN_RES, RESISTANCE, afe4404_res_table, ARRAY_SIZE(afe4404_res_table)); +static AFE440X_ATTR(tia_capacitance2, AFE4404_TIA_GAIN_SEP, AFE4404_TIA_GAIN_CAP, CAPACITANCE, afe4404_cap_table, ARRAY_SIZE(afe4404_cap_table)); + +static struct attribute *afe440x_attributes[] = { + &afe440x_attr_tia_separate_en.dev_attr.attr, + &afe440x_attr_tia_resistance1.dev_attr.attr, + &afe440x_attr_tia_capacitance1.dev_attr.attr, + &afe440x_attr_tia_resistance2.dev_attr.attr, + &afe440x_attr_tia_capacitance2.dev_attr.attr, + &dev_attr_tia_resistance_available.attr, + &dev_attr_tia_capacitance_available.attr, + NULL +}; + +static const struct attribute_group afe440x_attribute_group = { + .attrs = afe440x_attributes +}; + +static int afe4404_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct afe4404_data *afe = iio_priv(indio_dev); + const struct afe440x_reg_info reg_info = afe4404_reg_info[chan->address]; + int ret; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(afe->regmap, reg_info.reg, val); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_OFFSET: + ret = regmap_read(afe->regmap, reg_info.offreg, + val); + if (ret) + return ret; + *val &= reg_info.mask; + *val >>= reg_info.shift; + return IIO_VAL_INT; + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(afe->regmap, reg_info.reg, val); + if (ret) + return ret; + *val &= reg_info.mask; + *val >>= reg_info.shift; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + *val2 = 800000; + return IIO_VAL_INT_PLUS_MICRO; + } + break; + default: + break; + } + + return -EINVAL; +} + +static int afe4404_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct afe4404_data *afe = iio_priv(indio_dev); + const struct afe440x_reg_info reg_info = afe4404_reg_info[chan->address]; + + switch (chan->type) { + case IIO_INTENSITY: + switch (mask) { + case IIO_CHAN_INFO_OFFSET: + return regmap_update_bits(afe->regmap, + reg_info.offreg, + reg_info.mask, + (val << reg_info.shift)); + } + break; + case IIO_CURRENT: + switch (mask) { + case IIO_CHAN_INFO_RAW: + return regmap_update_bits(afe->regmap, + reg_info.reg, + reg_info.mask, + (val << reg_info.shift)); + } + break; + default: + break; + } + + return -EINVAL; +} + +static const struct iio_info afe4404_iio_info = { + .attrs = &afe440x_attribute_group, + .read_raw = afe4404_read_raw, + .write_raw = afe4404_write_raw, + .driver_module = THIS_MODULE, +}; + +static irqreturn_t afe4404_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct afe4404_data *afe = iio_priv(indio_dev); + int ret, bit, i = 0; + s32 buffer[10]; + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = regmap_read(afe->regmap, afe4404_reg_info[bit].reg, + &buffer[i++]); + if (ret) + goto err; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops afe4404_trigger_ops = { + .owner = THIS_MODULE, +}; + +/* Default timings from data-sheet */ +#define AFE4404_TIMING_PAIRS \ + { AFE440X_PRPCOUNT, 39999 }, \ + { AFE440X_LED2LEDSTC, 0 }, \ + { AFE440X_LED2LEDENDC, 398 }, \ + { AFE440X_LED2STC, 80 }, \ + { AFE440X_LED2ENDC, 398 }, \ + { AFE440X_ADCRSTSTCT0, 5600 }, \ + { AFE440X_ADCRSTENDCT0, 5606 }, \ + { AFE440X_LED2CONVST, 5607 }, \ + { AFE440X_LED2CONVEND, 6066 }, \ + { AFE4404_LED3LEDSTC, 400 }, \ + { AFE4404_LED3LEDENDC, 798 }, \ + { AFE440X_ALED2STC, 480 }, \ + { AFE440X_ALED2ENDC, 798 }, \ + { AFE440X_ADCRSTSTCT1, 6068 }, \ + { AFE440X_ADCRSTENDCT1, 6074 }, \ + { AFE440X_ALED2CONVST, 6075 }, \ + { AFE440X_ALED2CONVEND, 6534 }, \ + { AFE440X_LED1LEDSTC, 800 }, \ + { AFE440X_LED1LEDENDC, 1198 }, \ + { AFE440X_LED1STC, 880 }, \ + { AFE440X_LED1ENDC, 1198 }, \ + { AFE440X_ADCRSTSTCT2, 6536 }, \ + { AFE440X_ADCRSTENDCT2, 6542 }, \ + { AFE440X_LED1CONVST, 6543 }, \ + { AFE440X_LED1CONVEND, 7003 }, \ + { AFE440X_ALED1STC, 1280 }, \ + { AFE440X_ALED1ENDC, 1598 }, \ + { AFE440X_ADCRSTSTCT3, 7005 }, \ + { AFE440X_ADCRSTENDCT3, 7011 }, \ + { AFE440X_ALED1CONVST, 7012 }, \ + { AFE440X_ALED1CONVEND, 7471 }, \ + { AFE440X_PDNCYCLESTC, 7671 }, \ + { AFE440X_PDNCYCLEENDC, 39199 } + +static const struct reg_sequence afe4404_reg_sequences[] = { + AFE4404_TIMING_PAIRS, + { AFE440X_CONTROL1, AFE440X_CONTROL1_TIMEREN }, + { AFE4404_TIA_GAIN, AFE4404_TIA_GAIN_RES_50_K }, + { AFE440X_LEDCNTRL, (0xf << AFE4404_LEDCNTRL_ILED1_SHIFT) | + (0x3 << AFE4404_LEDCNTRL_ILED2_SHIFT) | + (0x3 << AFE4404_LEDCNTRL_ILED3_SHIFT) }, + { AFE440X_CONTROL2, AFE440X_CONTROL3_OSC_ENABLE }, +}; + +static const struct regmap_range afe4404_yes_ranges[] = { + regmap_reg_range(AFE440X_LED2VAL, AFE440X_LED1_ALED1VAL), + regmap_reg_range(AFE4404_AVG_LED2_ALED2VAL, AFE4404_AVG_LED1_ALED1VAL), +}; + +static const struct regmap_access_table afe4404_volatile_table = { + .yes_ranges = afe4404_yes_ranges, + .n_yes_ranges = ARRAY_SIZE(afe4404_yes_ranges), +}; + +static const struct regmap_config afe4404_regmap_config = { + .reg_bits = 8, + .val_bits = 24, + + .max_register = AFE4404_AVG_LED1_ALED1VAL, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &afe4404_volatile_table, +}; + +#ifdef CONFIG_OF +static const struct of_device_id afe4404_of_match[] = { + { .compatible = "ti,afe4404", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, afe4404_of_match); +#endif + +static int __maybe_unused afe4404_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + int ret; + + ret = regmap_update_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE, + AFE440X_CONTROL2_PDN_AFE); + if (ret) + return ret; + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static int __maybe_unused afe4404_resume(struct device *dev) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct afe4404_data *afe = iio_priv(indio_dev); + int ret; + + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_update_bits(afe->regmap, AFE440X_CONTROL2, + AFE440X_CONTROL2_PDN_AFE, 0); + if (ret) + return ret; + + return 0; +} + +static SIMPLE_DEV_PM_OPS(afe4404_pm_ops, afe4404_suspend, afe4404_resume); + +static int afe4404_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct afe4404_data *afe; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*afe)); + if (!indio_dev) + return -ENOMEM; + + afe = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + afe->dev = &client->dev; + afe->irq = client->irq; + + afe->regmap = devm_regmap_init_i2c(client, &afe4404_regmap_config); + if (IS_ERR(afe->regmap)) { + dev_err(afe->dev, "Unable to allocate register map\n"); + return PTR_ERR(afe->regmap); + } + + afe->regulator = devm_regulator_get(afe->dev, "tx_sup"); + if (IS_ERR(afe->regulator)) { + dev_err(afe->dev, "Unable to get regulator\n"); + return PTR_ERR(afe->regulator); + } + ret = regulator_enable(afe->regulator); + if (ret) { + dev_err(afe->dev, "Unable to enable regulator\n"); + return ret; + } + + ret = regmap_write(afe->regmap, AFE440X_CONTROL0, + AFE440X_CONTROL0_SW_RESET); + if (ret) { + dev_err(afe->dev, "Unable to reset device\n"); + goto disable_reg; + } + + ret = regmap_multi_reg_write(afe->regmap, afe4404_reg_sequences, + ARRAY_SIZE(afe4404_reg_sequences)); + if (ret) { + dev_err(afe->dev, "Unable to set register defaults\n"); + goto disable_reg; + } + + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->dev.parent = afe->dev; + indio_dev->channels = afe4404_channels; + indio_dev->num_channels = ARRAY_SIZE(afe4404_channels); + indio_dev->name = AFE4404_DRIVER_NAME; + indio_dev->info = &afe4404_iio_info; + + if (afe->irq > 0) { + afe->trig = devm_iio_trigger_alloc(afe->dev, + "%s-dev%d", + indio_dev->name, + indio_dev->id); + if (!afe->trig) { + dev_err(afe->dev, "Unable to allocate IIO trigger\n"); + ret = -ENOMEM; + goto disable_reg; + } + + iio_trigger_set_drvdata(afe->trig, indio_dev); + + afe->trig->ops = &afe4404_trigger_ops; + afe->trig->dev.parent = afe->dev; + + ret = iio_trigger_register(afe->trig); + if (ret) { + dev_err(afe->dev, "Unable to register IIO trigger\n"); + goto disable_reg; + } + + ret = devm_request_threaded_irq(afe->dev, afe->irq, + iio_trigger_generic_data_rdy_poll, + NULL, IRQF_ONESHOT, + AFE4404_DRIVER_NAME, + afe->trig); + if (ret) { + dev_err(afe->dev, "Unable to request IRQ\n"); + goto disable_reg; + } + } + + ret = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + afe4404_trigger_handler, NULL); + if (ret) { + dev_err(afe->dev, "Unable to setup buffer\n"); + goto unregister_trigger; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(afe->dev, "Unable to register IIO device\n"); + goto unregister_triggered_buffer; + } + + return 0; + +unregister_triggered_buffer: + iio_triggered_buffer_cleanup(indio_dev); +unregister_trigger: + if (afe->irq > 0) + iio_trigger_unregister(afe->trig); +disable_reg: + regulator_disable(afe->regulator); + + return ret; +} + +static int afe4404_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct afe4404_data *afe = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + + iio_triggered_buffer_cleanup(indio_dev); + + if (afe->irq > 0) + iio_trigger_unregister(afe->trig); + + ret = regulator_disable(afe->regulator); + if (ret) { + dev_err(afe->dev, "Unable to disable regulator\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id afe4404_ids[] = { + { "afe4404", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, afe4404_ids); + +static struct i2c_driver afe4404_i2c_driver = { + .driver = { + .name = AFE4404_DRIVER_NAME, + .of_match_table = of_match_ptr(afe4404_of_match), + .pm = &afe4404_pm_ops, + }, + .probe = afe4404_probe, + .remove = afe4404_remove, + .id_table = afe4404_ids, +}; +module_i2c_driver(afe4404_i2c_driver); + +MODULE_AUTHOR("Andrew F. Davis "); +MODULE_DESCRIPTION("TI AFE4404 Heart Rate and Pulse Oximeter"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/health/afe440x.h b/drivers/iio/health/afe440x.h new file mode 100644 index 0000000..c671ab7 --- /dev/null +++ b/drivers/iio/health/afe440x.h @@ -0,0 +1,191 @@ +/* + * AFE440X Heart Rate Monitors and Low-Cost Pulse Oximeters + * + * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ + * Andrew F. Davis + * + * 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. + * + * 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 _AFE440X_H +#define _AFE440X_H + +/* AFE440X registers */ +#define AFE440X_CONTROL0 0x00 +#define AFE440X_LED2STC 0x01 +#define AFE440X_LED2ENDC 0x02 +#define AFE440X_LED1LEDSTC 0x03 +#define AFE440X_LED1LEDENDC 0x04 +#define AFE440X_ALED2STC 0x05 +#define AFE440X_ALED2ENDC 0x06 +#define AFE440X_LED1STC 0x07 +#define AFE440X_LED1ENDC 0x08 +#define AFE440X_LED2LEDSTC 0x09 +#define AFE440X_LED2LEDENDC 0x0a +#define AFE440X_ALED1STC 0x0b +#define AFE440X_ALED1ENDC 0x0c +#define AFE440X_LED2CONVST 0x0d +#define AFE440X_LED2CONVEND 0x0e +#define AFE440X_ALED2CONVST 0x0f +#define AFE440X_ALED2CONVEND 0x10 +#define AFE440X_LED1CONVST 0x11 +#define AFE440X_LED1CONVEND 0x12 +#define AFE440X_ALED1CONVST 0x13 +#define AFE440X_ALED1CONVEND 0x14 +#define AFE440X_ADCRSTSTCT0 0x15 +#define AFE440X_ADCRSTENDCT0 0x16 +#define AFE440X_ADCRSTSTCT1 0x17 +#define AFE440X_ADCRSTENDCT1 0x18 +#define AFE440X_ADCRSTSTCT2 0x19 +#define AFE440X_ADCRSTENDCT2 0x1a +#define AFE440X_ADCRSTSTCT3 0x1b +#define AFE440X_ADCRSTENDCT3 0x1c +#define AFE440X_PRPCOUNT 0x1d +#define AFE440X_CONTROL1 0x1e +#define AFE440X_LEDCNTRL 0x22 +#define AFE440X_CONTROL2 0x23 +#define AFE440X_ALARM 0x29 +#define AFE440X_LED2VAL 0x2a +#define AFE440X_ALED2VAL 0x2b +#define AFE440X_LED1VAL 0x2c +#define AFE440X_ALED1VAL 0x2d +#define AFE440X_LED2_ALED2VAL 0x2e +#define AFE440X_LED1_ALED1VAL 0x2f +#define AFE440X_CONTROL3 0x31 +#define AFE440X_PDNCYCLESTC 0x32 +#define AFE440X_PDNCYCLEENDC 0x33 + +/* CONTROL0 register fields */ +#define AFE440X_CONTROL0_REG_READ BIT(0) +#define AFE440X_CONTROL0_TM_COUNT_RST BIT(1) +#define AFE440X_CONTROL0_SW_RESET BIT(3) + +/* CONTROL1 register fields */ +#define AFE440X_CONTROL1_TIMEREN BIT(8) + +/* TIAGAIN register fields */ +#define AFE440X_TIAGAIN_ENSEPGAIN_MASK BIT(15) +#define AFE440X_TIAGAIN_ENSEPGAIN_SHIFT 15 + +/* CONTROL2 register fields */ +#define AFE440X_CONTROL2_PDN_AFE BIT(0) +#define AFE440X_CONTROL2_PDN_RX BIT(1) +#define AFE440X_CONTROL2_DYNAMIC4 BIT(3) +#define AFE440X_CONTROL2_DYNAMIC3 BIT(4) +#define AFE440X_CONTROL2_DYNAMIC2 BIT(14) +#define AFE440X_CONTROL2_DYNAMIC1 BIT(20) + +/* CONTROL3 register fields */ +#define AFE440X_CONTROL3_CLKDIV GENMASK(2, 0) + +/* CONTROL0 values */ +#define AFE440X_CONTROL0_WRITE 0x0 +#define AFE440X_CONTROL0_READ 0x1 + +struct afe440x_reg_info { + unsigned int reg; + unsigned int offreg; + unsigned int shift; + unsigned int mask; +}; + +#define AFE440X_REG_INFO(_reg, _offreg, _sm) \ + { \ + .reg = _reg, \ + .offreg = _offreg, \ + .shift = _sm ## _SHIFT, \ + .mask = _sm ## _MASK, \ + } + +#define AFE440X_INTENSITY_CHAN(_index, _name, _mask) \ + { \ + .type = IIO_INTENSITY, \ + .channel = _index, \ + .address = _index, \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 24, \ + .storagebits = 32, \ + .endianness = IIO_CPU, \ + }, \ + .extend_name = _name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + _mask, \ + } + +#define AFE440X_CURRENT_CHAN(_index, _name) \ + { \ + .type = IIO_CURRENT, \ + .channel = _index, \ + .address = _index, \ + .scan_index = _index, \ + .extend_name = _name, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .output = true, \ + } + +enum afe440x_reg_type { + SIMPLE, + RESISTANCE, + CAPACITANCE, +}; + +struct afe440x_val_table { + int integer; + int fract; +}; + +#define AFE440X_TABLE_ATTR(_name, _table) \ +static ssize_t _name ## _show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + ssize_t len = 0; \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(_table); i++) \ + len += scnprintf(buf + len, PAGE_SIZE - len, "%d.%06u ", \ + _table[i].integer, \ + _table[i].fract); \ + \ + buf[len - 1] = '\n'; \ + \ + return len; \ +} \ +static DEVICE_ATTR_RO(_name) + +struct afe440x_attr { + struct device_attribute dev_attr; + unsigned int reg; + unsigned int shift; + unsigned int mask; + enum afe440x_reg_type type; + const struct afe440x_val_table *val_table; + unsigned int table_size; +}; + +#define to_afe440x_attr(_dev_attr) \ + container_of(_dev_attr, struct afe440x_attr, dev_attr) + +#define AFE440X_ATTR(_name, _reg, _field, _type, _table, _size) \ + struct afe440x_attr afe440x_attr_##_name = { \ + .dev_attr = __ATTR(_name, (S_IRUGO | S_IWUSR), \ + afe440x_show_register, \ + afe440x_store_register), \ + .reg = _reg, \ + .shift = _field ## _SHIFT, \ + .mask = _field ## _MASK, \ + .type = _type, \ + .val_table = _table, \ + .table_size = _size, \ + } + +#endif /* _AFE440X_H */ diff --git b/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c new file mode 100644 index 0000000..90ab8a2 --- /dev/null +++ b/drivers/iio/health/max30100.c @@ -0,0 +1,523 @@ +/* + * max30100.c - Support for MAX30100 heart rate and pulse oximeter sensor + * + * Copyright (C) 2015 Matt Ranostay + * + * 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. + * + * TODO: enable pulse length controls via device tree properties + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX30100_REGMAP_NAME "max30100_regmap" +#define MAX30100_DRV_NAME "max30100" + +#define MAX30100_REG_INT_STATUS 0x00 +#define MAX30100_REG_INT_STATUS_PWR_RDY BIT(0) +#define MAX30100_REG_INT_STATUS_SPO2_RDY BIT(4) +#define MAX30100_REG_INT_STATUS_HR_RDY BIT(5) +#define MAX30100_REG_INT_STATUS_FIFO_RDY BIT(7) + +#define MAX30100_REG_INT_ENABLE 0x01 +#define MAX30100_REG_INT_ENABLE_SPO2_EN BIT(0) +#define MAX30100_REG_INT_ENABLE_HR_EN BIT(1) +#define MAX30100_REG_INT_ENABLE_FIFO_EN BIT(3) +#define MAX30100_REG_INT_ENABLE_MASK 0xf0 +#define MAX30100_REG_INT_ENABLE_MASK_SHIFT 4 + +#define MAX30100_REG_FIFO_WR_PTR 0x02 +#define MAX30100_REG_FIFO_OVR_CTR 0x03 +#define MAX30100_REG_FIFO_RD_PTR 0x04 +#define MAX30100_REG_FIFO_DATA 0x05 +#define MAX30100_REG_FIFO_DATA_ENTRY_COUNT 16 +#define MAX30100_REG_FIFO_DATA_ENTRY_LEN 4 + +#define MAX30100_REG_MODE_CONFIG 0x06 +#define MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN BIT(0) +#define MAX30100_REG_MODE_CONFIG_MODE_HR_EN BIT(1) +#define MAX30100_REG_MODE_CONFIG_MODE_MASK 0x03 +#define MAX30100_REG_MODE_CONFIG_TEMP_EN BIT(3) +#define MAX30100_REG_MODE_CONFIG_PWR BIT(7) + +#define MAX30100_REG_SPO2_CONFIG 0x07 +#define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2) +#define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6) +#define MAX30100_REG_SPO2_CONFIG_1600US 0x3 + +#define MAX30100_REG_LED_CONFIG 0x09 +#define MAX30100_REG_LED_CONFIG_LED_MASK 0x0f +#define MAX30100_REG_LED_CONFIG_RED_LED_SHIFT 4 + +#define MAX30100_REG_LED_CONFIG_24MA 0x07 +#define MAX30100_REG_LED_CONFIG_50MA 0x0f + +#define MAX30100_REG_TEMP_INTEGER 0x16 +#define MAX30100_REG_TEMP_FRACTION 0x17 + +struct max30100_data { + struct i2c_client *client; + struct iio_dev *indio_dev; + struct mutex lock; + struct regmap *regmap; + + __be16 buffer[2]; /* 2 16-bit channels */ +}; + +static bool max30100_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX30100_REG_INT_STATUS: + case MAX30100_REG_MODE_CONFIG: + case MAX30100_REG_FIFO_WR_PTR: + case MAX30100_REG_FIFO_OVR_CTR: + case MAX30100_REG_FIFO_RD_PTR: + case MAX30100_REG_FIFO_DATA: + case MAX30100_REG_TEMP_INTEGER: + case MAX30100_REG_TEMP_FRACTION: + return true; + default: + return false; + } +} + +static const struct regmap_config max30100_regmap_config = { + .name = MAX30100_REGMAP_NAME, + + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX30100_REG_TEMP_FRACTION, + .cache_type = REGCACHE_FLAT, + + .volatile_reg = max30100_is_volatile_reg, +}; + +static const unsigned int max30100_led_current_mapping[] = { + 4400, 7600, 11000, 14200, 17400, + 20800, 24000, 27100, 30600, 33800, + 37000, 40200, 43600, 46800, 50000 +}; + +static const unsigned long max30100_scan_masks[] = {0x3, 0}; + +static const struct iio_chan_spec max30100_channels[] = { + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_IR, + .modified = 1, + + .scan_index = 0, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_INTENSITY, + .channel2 = IIO_MOD_LIGHT_RED, + .modified = 1, + + .scan_index = 1, + .scan_type = { + .sign = 'u', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_BE, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, +}; + +static int max30100_set_powermode(struct max30100_data *data, bool state) +{ + return regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_PWR, + state ? 0 : MAX30100_REG_MODE_CONFIG_PWR); +} + +static int max30100_clear_fifo(struct max30100_data *data) +{ + int ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_WR_PTR, 0); + if (ret) + return ret; + + ret = regmap_write(data->regmap, MAX30100_REG_FIFO_OVR_CTR, 0); + if (ret) + return ret; + + return regmap_write(data->regmap, MAX30100_REG_FIFO_RD_PTR, 0); +} + +static int max30100_buffer_postenable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret; + + ret = max30100_set_powermode(data, true); + if (ret) + return ret; + + return max30100_clear_fifo(data); +} + +static int max30100_buffer_predisable(struct iio_dev *indio_dev) +{ + struct max30100_data *data = iio_priv(indio_dev); + + return max30100_set_powermode(data, false); +} + +static const struct iio_buffer_setup_ops max30100_buffer_setup_ops = { + .postenable = max30100_buffer_postenable, + .predisable = max30100_buffer_predisable, +}; + +static inline int max30100_fifo_count(struct max30100_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX30100_REG_INT_STATUS, &val); + if (ret) + return ret; + + /* FIFO is almost full */ + if (val & MAX30100_REG_INT_STATUS_FIFO_RDY) + return MAX30100_REG_FIFO_DATA_ENTRY_COUNT - 1; + + return 0; +} + +static int max30100_read_measurement(struct max30100_data *data) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(data->client, + MAX30100_REG_FIFO_DATA, + MAX30100_REG_FIFO_DATA_ENTRY_LEN, + (u8 *) &data->buffer); + + return (ret == MAX30100_REG_FIFO_DATA_ENTRY_LEN) ? 0 : ret; +} + +static irqreturn_t max30100_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct max30100_data *data = iio_priv(indio_dev); + int ret, cnt = 0; + + mutex_lock(&data->lock); + + while (cnt || (cnt = max30100_fifo_count(data) > 0)) { + ret = max30100_read_measurement(data); + if (ret) + break; + + iio_push_to_buffers(data->indio_dev, data->buffer); + cnt--; + } + + mutex_unlock(&data->lock); + + return IRQ_HANDLED; +} + +static int max30100_get_current_idx(unsigned int val, int *reg) +{ + int idx; + + /* LED turned off */ + if (val == 0) { + *reg = 0; + return 0; + } + + for (idx = 0; idx < ARRAY_SIZE(max30100_led_current_mapping); idx++) { + if (max30100_led_current_mapping[idx] == val) { + *reg = idx + 1; + return 0; + } + } + + return -EINVAL; +} + +static int max30100_led_init(struct max30100_data *data) +{ + struct device *dev = &data->client->dev; + struct device_node *np = dev->of_node; + unsigned int val[2]; + int reg, ret; + + ret = of_property_read_u32_array(np, "maxim,led-current-microamp", + (unsigned int *) &val, 2); + if (ret) { + /* Default to 24 mA RED LED, 50 mA IR LED */ + reg = (MAX30100_REG_LED_CONFIG_24MA << + MAX30100_REG_LED_CONFIG_RED_LED_SHIFT) | + MAX30100_REG_LED_CONFIG_50MA; + dev_warn(dev, "no led-current-microamp set"); + + return regmap_write(data->regmap, MAX30100_REG_LED_CONFIG, reg); + } + + /* RED LED current */ + ret = max30100_get_current_idx(val[0], ®); + if (ret) { + dev_err(dev, "invalid RED current setting %d", val[0]); + return ret; + } + + ret = regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG, + MAX30100_REG_LED_CONFIG_LED_MASK << + MAX30100_REG_LED_CONFIG_RED_LED_SHIFT, + reg << MAX30100_REG_LED_CONFIG_RED_LED_SHIFT); + if (ret) + return ret; + + /* IR LED current */ + ret = max30100_get_current_idx(val[1], ®); + if (ret) { + dev_err(dev, "invalid IR current setting %d", val[1]); + return ret; + } + + return regmap_update_bits(data->regmap, MAX30100_REG_LED_CONFIG, + MAX30100_REG_LED_CONFIG_LED_MASK, reg); +} + +static int max30100_chip_init(struct max30100_data *data) +{ + int ret; + + /* setup LED current settings */ + ret = max30100_led_init(data); + if (ret) + return ret; + + /* enable hi-res SPO2 readings at 100Hz */ + ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG, + MAX30100_REG_SPO2_CONFIG_HI_RES_EN | + MAX30100_REG_SPO2_CONFIG_100HZ); + if (ret) + return ret; + + /* enable SPO2 mode */ + ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_MODE_MASK, + MAX30100_REG_MODE_CONFIG_MODE_HR_EN | + MAX30100_REG_MODE_CONFIG_MODE_SPO2_EN); + if (ret) + return ret; + + /* enable FIFO interrupt */ + return regmap_update_bits(data->regmap, MAX30100_REG_INT_ENABLE, + MAX30100_REG_INT_ENABLE_MASK, + MAX30100_REG_INT_ENABLE_FIFO_EN + << MAX30100_REG_INT_ENABLE_MASK_SHIFT); +} + +static int max30100_read_temp(struct max30100_data *data, int *val) +{ + int ret; + unsigned int reg; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_INTEGER, ®); + if (ret < 0) + return ret; + *val = reg << 4; + + ret = regmap_read(data->regmap, MAX30100_REG_TEMP_FRACTION, ®); + if (ret < 0) + return ret; + + *val |= reg & 0xf; + *val = sign_extend32(*val, 11); + + return 0; +} + +static int max30100_get_temp(struct max30100_data *data, int *val) +{ + int ret; + + /* start acquisition */ + ret = regmap_update_bits(data->regmap, MAX30100_REG_MODE_CONFIG, + MAX30100_REG_MODE_CONFIG_TEMP_EN, + MAX30100_REG_MODE_CONFIG_TEMP_EN); + if (ret) + return ret; + + usleep_range(35000, 50000); + + return max30100_read_temp(data, val); +} + +static int max30100_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max30100_data *data = iio_priv(indio_dev); + int ret = -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + /* + * Temperature reading can only be acquired while engine + * is running + */ + mutex_lock(&indio_dev->mlock); + + if (!iio_buffer_enabled(indio_dev)) + ret = -EAGAIN; + else { + ret = max30100_get_temp(data, val); + if (!ret) + ret = IIO_VAL_INT; + + } + + mutex_unlock(&indio_dev->mlock); + break; + case IIO_CHAN_INFO_SCALE: + *val = 1; /* 0.0625 */ + *val2 = 16; + ret = IIO_VAL_FRACTIONAL; + break; + } + + return ret; +} + +static const struct iio_info max30100_info = { + .driver_module = THIS_MODULE, + .read_raw = max30100_read_raw, +}; + +static int max30100_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max30100_data *data; + struct iio_buffer *buffer; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + buffer = devm_iio_kfifo_allocate(&client->dev); + if (!buffer) + return -ENOMEM; + + iio_device_attach_buffer(indio_dev, buffer); + + indio_dev->name = MAX30100_DRV_NAME; + indio_dev->channels = max30100_channels; + indio_dev->info = &max30100_info; + indio_dev->num_channels = ARRAY_SIZE(max30100_channels); + indio_dev->available_scan_masks = max30100_scan_masks; + indio_dev->modes = (INDIO_BUFFER_SOFTWARE | INDIO_DIRECT_MODE); + indio_dev->setup_ops = &max30100_buffer_setup_ops; + + data = iio_priv(indio_dev); + data->indio_dev = indio_dev; + data->client = client; + + mutex_init(&data->lock); + i2c_set_clientdata(client, indio_dev); + + data->regmap = devm_regmap_init_i2c(client, &max30100_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap initialization failed.\n"); + return PTR_ERR(data->regmap); + } + max30100_set_powermode(data, false); + + ret = max30100_chip_init(data); + if (ret) + return ret; + + if (client->irq <= 0) { + dev_err(&client->dev, "no valid irq defined\n"); + return -EINVAL; + } + ret = devm_request_threaded_irq(&client->dev, client->irq, + NULL, max30100_interrupt_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max30100_irq", indio_dev); + if (ret) { + dev_err(&client->dev, "request irq (%d) failed\n", client->irq); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int max30100_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct max30100_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + max30100_set_powermode(data, false); + + return 0; +} + +static const struct i2c_device_id max30100_id[] = { + { "max30100", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, max30100_id); + +static const struct of_device_id max30100_dt_ids[] = { + { .compatible = "maxim,max30100" }, + { } +}; +MODULE_DEVICE_TABLE(of, max30100_dt_ids); + +static struct i2c_driver max30100_driver = { + .driver = { + .name = MAX30100_DRV_NAME, + .of_match_table = of_match_ptr(max30100_dt_ids), + }, + .probe = max30100_probe, + .remove = max30100_remove, + .id_table = max30100_id, +}; +module_i2c_driver(max30100_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("MAX30100 heart rate and pulse oximeter sensor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig index 6a23698..738a86d 100644 --- a/drivers/iio/humidity/Kconfig +++ b/drivers/iio/humidity/Kconfig @@ -3,6 +3,16 @@ # menu "Humidity sensors" +config AM2315 + tristate "Aosong AM2315 relative humidity and temperature sensor" + depends on I2C + help + If you say yes here you get support for the Aosong AM2315 + relative humidity and ambient temperature sensor. + + This driver can also be built as a module. If so, the module will + be called am2315. + config DHT11 tristate "DHT11 (and compatible sensors) driver" depends on GPIOLIB || COMPILE_TEST @@ -43,14 +53,16 @@ config SI7005 humidity and temperature sensor. To compile this driver as a module, choose M here: the module - will be called si7005. + will be called si7005. This driver also + supports Hoperf TH02 Humidity and Temperature Sensor. config SI7020 tristate "Si7013/20/21 Relative Humidity and Temperature Sensors" depends on I2C help Say yes here to build support for the Silicon Labs Si7013/20/21 - Relative Humidity and Temperature Sensors. + Relative Humidity and Temperature Sensors. This driver also + supports Hoperf TH06 Humidity and Temperature Sensor. To compile this driver as a module, choose M here: the module will be called si7020. diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile index c9f089a..4a73442 100644 --- a/drivers/iio/humidity/Makefile +++ b/drivers/iio/humidity/Makefile @@ -2,6 +2,7 @@ # Makefile for IIO humidity sensor drivers # +obj-$(CONFIG_AM2315) += am2315.o obj-$(CONFIG_DHT11) += dht11.o obj-$(CONFIG_HDC100X) += hdc100x.o obj-$(CONFIG_HTU21) += htu21.o diff --git b/drivers/iio/humidity/am2315.c b/drivers/iio/humidity/am2315.c new file mode 100644 index 0000000..1153591 --- /dev/null +++ b/drivers/iio/humidity/am2315.c @@ -0,0 +1,301 @@ +/** + * Aosong AM2315 relative humidity and temperature + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * 7-bit I2C address: 0x5C. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AM2315_REG_HUM_MSB 0x00 +#define AM2315_REG_HUM_LSB 0x01 +#define AM2315_REG_TEMP_MSB 0x02 +#define AM2315_REG_TEMP_LSB 0x03 + +#define AM2315_FUNCTION_READ 0x03 +#define AM2315_HUM_OFFSET 2 +#define AM2315_TEMP_OFFSET 4 +#define AM2315_ALL_CHANNEL_MASK GENMASK(1, 0) + +#define AM2315_DRIVER_NAME "am2315" + +struct am2315_data { + struct i2c_client *client; + struct mutex lock; + s16 buffer[8]; /* 2x16-bit channels + 2x16 padding + 4x16 timestamp */ +}; + +struct am2315_sensor_data { + s16 hum_data; + s16 temp_data; +}; + +static const struct iio_chan_spec am2315_channels[] = { + { + .type = IIO_HUMIDITYRELATIVE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 16, + .storagebits = 16, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), +}; + +/* CRC calculation algorithm, as specified in the datasheet (page 13). */ +static u16 am2315_crc(u8 *data, u8 nr_bytes) +{ + int i; + u16 crc = 0xffff; + + while (nr_bytes--) { + crc ^= *data++; + for (i = 0; i < 8; i++) { + if (crc & 0x01) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + + return crc; +} + +/* Simple function that sends a few bytes to the device to wake it up. */ +static void am2315_ping(struct i2c_client *client) +{ + i2c_smbus_read_byte_data(client, AM2315_REG_HUM_MSB); +} + +static int am2315_read_data(struct am2315_data *data, + struct am2315_sensor_data *sensor_data) +{ + int ret; + /* tx_buf format: */ + u8 tx_buf[3] = { AM2315_FUNCTION_READ, AM2315_REG_HUM_MSB, 4 }; + /* + * rx_buf format: + * + * + * + */ + u8 rx_buf[8]; + u16 crc; + + /* First wake up the device. */ + am2315_ping(data->client); + + mutex_lock(&data->lock); + ret = i2c_master_send(data->client, tx_buf, sizeof(tx_buf)); + if (ret < 0) { + dev_err(&data->client->dev, "failed to send read request\n"); + goto exit_unlock; + } + /* Wait 2-3 ms, then read back the data sent by the device. */ + usleep_range(2000, 3000); + /* Do a bulk data read, then pick out what we need. */ + ret = i2c_master_recv(data->client, rx_buf, sizeof(rx_buf)); + if (ret < 0) { + dev_err(&data->client->dev, "failed to read sensor data\n"); + goto exit_unlock; + } + mutex_unlock(&data->lock); + /* + * Do a CRC check on the data and compare it to the value + * calculated by the device. + */ + crc = am2315_crc(rx_buf, sizeof(rx_buf) - 2); + if ((crc & 0xff) != rx_buf[6] || (crc >> 8) != rx_buf[7]) { + dev_err(&data->client->dev, "failed to verify sensor data\n"); + return -EIO; + } + + sensor_data->hum_data = (rx_buf[AM2315_HUM_OFFSET] << 8) | + rx_buf[AM2315_HUM_OFFSET + 1]; + sensor_data->temp_data = (rx_buf[AM2315_TEMP_OFFSET] << 8) | + rx_buf[AM2315_TEMP_OFFSET + 1]; + + return ret; + +exit_unlock: + mutex_unlock(&data->lock); + return ret; +} + +static irqreturn_t am2315_trigger_handler(int irq, void *p) +{ + int i; + int ret; + int bit; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct am2315_data *data = iio_priv(indio_dev); + struct am2315_sensor_data sensor_data; + + ret = am2315_read_data(data, &sensor_data); + if (ret < 0) + goto err; + + mutex_lock(&data->lock); + if (*(indio_dev->active_scan_mask) == AM2315_ALL_CHANNEL_MASK) { + data->buffer[0] = sensor_data.hum_data; + data->buffer[1] = sensor_data.temp_data; + } else { + i = 0; + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + data->buffer[i] = (bit ? sensor_data.temp_data : + sensor_data.hum_data); + i++; + } + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + pf->timestamp); +err: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int am2315_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct am2315_sensor_data sensor_data; + struct am2315_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = am2315_read_data(data, &sensor_data); + if (ret < 0) + return ret; + *val = (chan->type == IIO_HUMIDITYRELATIVE) ? + sensor_data.hum_data : sensor_data.temp_data; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 100; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static const struct iio_info am2315_info = { + .driver_module = THIS_MODULE, + .read_raw = am2315_read_raw, +}; + +static int am2315_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct iio_dev *indio_dev; + struct am2315_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) { + dev_err(&client->dev, "iio allocation failed!\n"); + return -ENOMEM; + } + + data = iio_priv(indio_dev); + data->client = client; + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &am2315_info; + indio_dev->name = AM2315_DRIVER_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = am2315_channels; + indio_dev->num_channels = ARRAY_SIZE(am2315_channels); + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + am2315_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto err_buffer_cleanup; + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); + return ret; +} + +static int am2315_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static const struct i2c_device_id am2315_i2c_id[] = { + {"am2315", 0}, + {} +}; + +static const struct acpi_device_id am2315_acpi_id[] = { + {"AOS2315", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, am2315_acpi_id); + +static struct i2c_driver am2315_driver = { + .driver = { + .name = "am2315", + .acpi_match_table = ACPI_PTR(am2315_acpi_id), + }, + .probe = am2315_probe, + .remove = am2315_remove, + .id_table = am2315_i2c_id, +}; + +module_i2c_driver(am2315_driver); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("Aosong AM2315 relative humidity and temperature"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c index 1165b1c..9c47bc9 100644 --- a/drivers/iio/humidity/dht11.c +++ b/drivers/iio/humidity/dht11.c @@ -50,12 +50,32 @@ #define DHT11_EDGES_PER_READ (2 * DHT11_BITS_PER_READ + \ DHT11_EDGES_PREAMBLE + 1) -/* Data transmission timing (nano seconds) */ +/* + * Data transmission timing: + * Data bits are encoded as pulse length (high time) on the data line. + * 0-bit: 22-30uS -- typically 26uS (AM2302) + * 1-bit: 68-75uS -- typically 70uS (AM2302) + * The acutal timings also depend on the properties of the cable, with + * longer cables typically making pulses shorter. + * + * Our decoding depends on the time resolution of the system: + * timeres > 34uS ... don't know what a 1-tick pulse is + * 34uS > timeres > 30uS ... no problem (30kHz and 32kHz clocks) + * 30uS > timeres > 23uS ... don't know what a 2-tick pulse is + * timeres < 23uS ... no problem + * + * Luckily clocks in the 33-44kHz range are quite uncommon, so we can + * support most systems if the threshold for decoding a pulse as 1-bit + * is chosen carefully. If somebody really wants to support clocks around + * 40kHz, where this driver is most unreliable, there are two options. + * a) select an implementation using busy loop polling on those systems + * b) use the checksum to do some probabilistic decoding + */ #define DHT11_START_TRANSMISSION 18 /* ms */ -#define DHT11_SENSOR_RESPONSE 80000 -#define DHT11_START_BIT 50000 -#define DHT11_DATA_BIT_LOW 27000 -#define DHT11_DATA_BIT_HIGH 70000 +#define DHT11_MIN_TIMERES 34000 /* ns */ +#define DHT11_THRESHOLD 49000 /* ns */ +#define DHT11_AMBIG_LOW 23000 /* ns */ +#define DHT11_AMBIG_HIGH 30000 /* ns */ struct dht11 { struct device *dev; @@ -76,48 +96,68 @@ struct dht11 { struct {s64 ts; int value; } edges[DHT11_EDGES_PER_READ]; }; -static unsigned char dht11_decode_byte(int *timing, int threshold) +#ifdef CONFIG_DYNAMIC_DEBUG +/* + * dht11_edges_print: show the data as actually received by the + * driver. + */ +static void dht11_edges_print(struct dht11 *dht11) +{ + int i; + + dev_dbg(dht11->dev, "%d edges detected:\n", dht11->num_edges); + for (i = 1; i < dht11->num_edges; ++i) { + dev_dbg(dht11->dev, "%d: %lld ns %s\n", i, + dht11->edges[i].ts - dht11->edges[i - 1].ts, + dht11->edges[i - 1].value ? "high" : "low"); + } +} +#endif /* CONFIG_DYNAMIC_DEBUG */ + +static unsigned char dht11_decode_byte(char *bits) { unsigned char ret = 0; int i; for (i = 0; i < 8; ++i) { ret <<= 1; - if (timing[i] >= threshold) + if (bits[i]) ++ret; } return ret; } -static int dht11_decode(struct dht11 *dht11, int offset, int timeres) +static int dht11_decode(struct dht11 *dht11, int offset) { - int i, t, timing[DHT11_BITS_PER_READ], threshold; + int i, t; + char bits[DHT11_BITS_PER_READ]; unsigned char temp_int, temp_dec, hum_int, hum_dec, checksum; - threshold = DHT11_DATA_BIT_HIGH / timeres; - if (DHT11_DATA_BIT_LOW / timeres + 1 >= threshold) - pr_err("dht11: WARNING: decoding ambiguous\n"); - - /* scale down with timeres and check validity */ for (i = 0; i < DHT11_BITS_PER_READ; ++i) { t = dht11->edges[offset + 2 * i + 2].ts - dht11->edges[offset + 2 * i + 1].ts; - if (!dht11->edges[offset + 2 * i + 1].value) - return -EIO; /* lost synchronisation */ - timing[i] = t / timeres; + if (!dht11->edges[offset + 2 * i + 1].value) { + dev_dbg(dht11->dev, + "lost synchronisation at edge %d\n", + offset + 2 * i + 1); + return -EIO; + } + bits[i] = t > DHT11_THRESHOLD; } - hum_int = dht11_decode_byte(timing, threshold); - hum_dec = dht11_decode_byte(&timing[8], threshold); - temp_int = dht11_decode_byte(&timing[16], threshold); - temp_dec = dht11_decode_byte(&timing[24], threshold); - checksum = dht11_decode_byte(&timing[32], threshold); + hum_int = dht11_decode_byte(bits); + hum_dec = dht11_decode_byte(&bits[8]); + temp_int = dht11_decode_byte(&bits[16]); + temp_dec = dht11_decode_byte(&bits[24]); + checksum = dht11_decode_byte(&bits[32]); - if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) + if (((hum_int + hum_dec + temp_int + temp_dec) & 0xff) != checksum) { + dev_dbg(dht11->dev, "invalid checksum\n"); return -EIO; + } - dht11->timestamp = ktime_get_real_ns(); + dht11->timestamp = ktime_get_boot_ns(); if (hum_int < 20) { /* DHT22 */ dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) * ((temp_int & 0x80) ? -100 : 100); @@ -145,7 +185,7 @@ static irqreturn_t dht11_handle_irq(int irq, void *data) /* TODO: Consider making the handler safe for IRQ sharing */ if (dht11->num_edges < DHT11_EDGES_PER_READ && dht11->num_edges >= 0) { - dht11->edges[dht11->num_edges].ts = ktime_get_real_ns(); + dht11->edges[dht11->num_edges].ts = ktime_get_boot_ns(); dht11->edges[dht11->num_edges++].value = gpio_get_value(dht11->gpio); @@ -161,12 +201,13 @@ static int dht11_read_raw(struct iio_dev *iio_dev, int *val, int *val2, long m) { struct dht11 *dht11 = iio_priv(iio_dev); - int ret, timeres; + int ret, timeres, offset; mutex_lock(&dht11->lock); - if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_real_ns()) { + if (dht11->timestamp + DHT11_DATA_VALID_TIME < ktime_get_boot_ns()) { timeres = ktime_get_resolution_ns(); - if (DHT11_DATA_BIT_HIGH < 2 * timeres) { + dev_dbg(dht11->dev, "current timeresolution: %dns\n", timeres); + if (timeres > DHT11_MIN_TIMERES) { dev_err(dht11->dev, "timeresolution %dns too low\n", timeres); /* In theory a better clock could become available @@ -176,6 +217,10 @@ static int dht11_read_raw(struct iio_dev *iio_dev, ret = -EAGAIN; goto err; } + if (timeres > DHT11_AMBIG_LOW && timeres < DHT11_AMBIG_HIGH) + dev_warn(dht11->dev, + "timeresolution: %dns - decoding ambiguous\n", + timeres); reinit_completion(&dht11->completion); @@ -199,20 +244,26 @@ static int dht11_read_raw(struct iio_dev *iio_dev, free_irq(dht11->irq, iio_dev); +#ifdef CONFIG_DYNAMIC_DEBUG + dht11_edges_print(dht11); +#endif + if (ret == 0 && dht11->num_edges < DHT11_EDGES_PER_READ - 1) { - dev_err(&iio_dev->dev, - "Only %d signal edges detected\n", - dht11->num_edges); + dev_err(dht11->dev, "Only %d signal edges detected\n", + dht11->num_edges); ret = -ETIMEDOUT; } if (ret < 0) goto err; - ret = dht11_decode(dht11, - dht11->num_edges == DHT11_EDGES_PER_READ ? - DHT11_EDGES_PREAMBLE : - DHT11_EDGES_PREAMBLE - 2, - timeres); + offset = DHT11_EDGES_PREAMBLE + + dht11->num_edges - DHT11_EDGES_PER_READ; + for (; offset >= 0; --offset) { + ret = dht11_decode(dht11, offset); + if (!ret) + break; + } + if (ret) goto err; } @@ -279,7 +330,7 @@ static int dht11_probe(struct platform_device *pdev) return -EINVAL; } - dht11->timestamp = ktime_get_real_ns() - DHT11_DATA_VALID_TIME - 1; + dht11->timestamp = ktime_get_boot_ns() - DHT11_DATA_VALID_TIME - 1; dht11->num_edges = -1; platform_set_drvdata(pdev, iio); diff --git a/drivers/iio/humidity/hdc100x.c b/drivers/iio/humidity/hdc100x.c index dc5e7e7..a03832a 100644 --- a/drivers/iio/humidity/hdc100x.c +++ b/drivers/iio/humidity/hdc100x.c @@ -274,7 +274,7 @@ static int hdc100x_probe(struct i2c_client *client, if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) diff --git a/drivers/iio/humidity/htu21.c b/drivers/iio/humidity/htu21.c index d1636a7..11cbc38 100644 --- a/drivers/iio/humidity/htu21.c +++ b/drivers/iio/humidity/htu21.c @@ -192,7 +192,7 @@ static int htu21_probe(struct i2c_client *client, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { dev_err(&client->dev, "Adapter does not support some i2c transaction\n"); - return -ENODEV; + return -EOPNOTSUPP; } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); diff --git a/drivers/iio/humidity/si7005.c b/drivers/iio/humidity/si7005.c index 91972cc..6297766 100644 --- a/drivers/iio/humidity/si7005.c +++ b/drivers/iio/humidity/si7005.c @@ -135,7 +135,7 @@ static int si7005_probe(struct i2c_client *client, int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) @@ -170,6 +170,7 @@ static int si7005_probe(struct i2c_client *client, static const struct i2c_device_id si7005_id[] = { { "si7005", 0 }, + { "th02", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, si7005_id); diff --git a/drivers/iio/humidity/si7020.c b/drivers/iio/humidity/si7020.c index 71991b5..ffc2ccf 100644 --- a/drivers/iio/humidity/si7020.c +++ b/drivers/iio/humidity/si7020.c @@ -121,7 +121,7 @@ static int si7020_probe(struct i2c_client *client, if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE | I2C_FUNC_SMBUS_READ_WORD_DATA)) - return -ENODEV; + return -EOPNOTSUPP; /* Reset device, loads default settings. */ ret = i2c_smbus_write_byte(client, SI7020CMD_RESET); @@ -149,6 +149,7 @@ static int si7020_probe(struct i2c_client *client, static const struct i2c_device_id si7020_id[] = { { "si7020", 0 }, + { "th06", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, si7020_id); diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 5e610f7..1f1ad41 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -25,6 +25,8 @@ config ADIS16480 Say yes here to build support for Analog Devices ADIS16375, ADIS16480, ADIS16485, ADIS16488 inertial sensors. +source "drivers/iio/imu/bmi160/Kconfig" + config KMX61 tristate "Kionix KMX61 6-axis accelerometer and magnetometer" depends on I2C diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index e1e6e3d..c71bcd3 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -13,6 +13,7 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_trigger.o adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o +obj-y += bmi160/ obj-y += inv_mpu6050/ obj-$(CONFIG_KMX61) += kmx61.o diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c index 911255d..ad6f91d 100644 --- a/drivers/iio/imu/adis.c +++ b/drivers/iio/imu/adis.c @@ -324,7 +324,12 @@ static int adis_self_test(struct adis *adis) msleep(adis->data->startup_delay); - return adis_check_status(adis); + ret = adis_check_status(adis); + + if (adis->data->self_test_no_autoclear) + adis_write_reg_16(adis, adis->data->msc_ctrl_reg, 0x00); + + return ret; } /** diff --git a/drivers/iio/imu/adis16400_core.c b/drivers/iio/imu/adis16400_core.c index 0618f83..fb7c0db 100644 --- a/drivers/iio/imu/adis16400_core.c +++ b/drivers/iio/imu/adis16400_core.c @@ -288,7 +288,11 @@ static int adis16400_initial_setup(struct iio_dev *indio_dev) if (ret) goto err_ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) { + ret = -EINVAL; + goto err_ret; + } if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git a/drivers/iio/imu/adis16480.c b/drivers/iio/imu/adis16480.c index 2485b88..8cf84d3 100644 --- a/drivers/iio/imu/adis16480.c +++ b/drivers/iio/imu/adis16480.c @@ -765,7 +765,9 @@ static int adis16480_initial_setup(struct iio_dev *indio_dev) if (ret) return ret; - sscanf(indio_dev->name, "adis%u\n", &device_id); + ret = sscanf(indio_dev->name, "adis%u\n", &device_id); + if (ret != 1) + return -EINVAL; if (prod_id != device_id) dev_warn(&indio_dev->dev, "Device ID(%u) and product ID(%u) do not match.", diff --git b/drivers/iio/imu/bmi160/Kconfig b/drivers/iio/imu/bmi160/Kconfig new file mode 100644 index 0000000..005c17c --- /dev/null +++ b/drivers/iio/imu/bmi160/Kconfig @@ -0,0 +1,32 @@ +# +# BMI160 IMU driver +# + +config BMI160 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BMI160_I2C + tristate "Bosch BMI160 I2C driver" + depends on I2C + select BMI160 + select REGMAP_I2C + help + If you say yes here you get support for BMI160 IMU on I2C with + accelerometer, gyroscope and external BMG160 magnetometer. + + This driver can also be built as a module. If so, the module will be + called bmi160_i2c. + +config BMI160_SPI + tristate "Bosch BMI160 SPI driver" + depends on SPI + select BMI160 + select REGMAP_SPI + help + If you say yes here you get support for BMI160 IMU on SPI with + accelerometer, gyroscope and external BMG160 magnetometer. + + This driver can also be built as a module. If so, the module will be + called bmi160_spi. diff --git b/drivers/iio/imu/bmi160/Makefile b/drivers/iio/imu/bmi160/Makefile new file mode 100644 index 0000000..10365e4 --- /dev/null +++ b/drivers/iio/imu/bmi160/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for Bosch BMI160 IMU +# +obj-$(CONFIG_BMI160) += bmi160_core.o +obj-$(CONFIG_BMI160_I2C) += bmi160_i2c.o +obj-$(CONFIG_BMI160_SPI) += bmi160_spi.o diff --git b/drivers/iio/imu/bmi160/bmi160.h b/drivers/iio/imu/bmi160/bmi160.h new file mode 100644 index 0000000..d2ae6ed --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160.h @@ -0,0 +1,10 @@ +#ifndef BMI160_H_ +#define BMI160_H_ + +extern const struct regmap_config bmi160_regmap_config; + +int bmi160_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi); +void bmi160_core_remove(struct device *dev); + +#endif /* BMI160_H_ */ diff --git b/drivers/iio/imu/bmi160/bmi160_core.c b/drivers/iio/imu/bmi160/bmi160_core.c new file mode 100644 index 0000000..b8a290e --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160_core.c @@ -0,0 +1,596 @@ +/* + * BMI160 - Bosch IMU (accel, gyro plus external magnetometer) + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO core driver for BMI160, with support for I2C/SPI busses + * + * TODO: magnetometer, interrupts, hardware FIFO + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "bmi160.h" + +#define BMI160_REG_CHIP_ID 0x00 +#define BMI160_CHIP_ID_VAL 0xD1 + +#define BMI160_REG_PMU_STATUS 0x03 + +/* X axis data low byte address, the rest can be obtained using axis offset */ +#define BMI160_REG_DATA_MAGN_XOUT_L 0x04 +#define BMI160_REG_DATA_GYRO_XOUT_L 0x0C +#define BMI160_REG_DATA_ACCEL_XOUT_L 0x12 + +#define BMI160_REG_ACCEL_CONFIG 0x40 +#define BMI160_ACCEL_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI160_ACCEL_CONFIG_BWP_MASK GENMASK(6, 4) + +#define BMI160_REG_ACCEL_RANGE 0x41 +#define BMI160_ACCEL_RANGE_2G 0x03 +#define BMI160_ACCEL_RANGE_4G 0x05 +#define BMI160_ACCEL_RANGE_8G 0x08 +#define BMI160_ACCEL_RANGE_16G 0x0C + +#define BMI160_REG_GYRO_CONFIG 0x42 +#define BMI160_GYRO_CONFIG_ODR_MASK GENMASK(3, 0) +#define BMI160_GYRO_CONFIG_BWP_MASK GENMASK(5, 4) + +#define BMI160_REG_GYRO_RANGE 0x43 +#define BMI160_GYRO_RANGE_2000DPS 0x00 +#define BMI160_GYRO_RANGE_1000DPS 0x01 +#define BMI160_GYRO_RANGE_500DPS 0x02 +#define BMI160_GYRO_RANGE_250DPS 0x03 +#define BMI160_GYRO_RANGE_125DPS 0x04 + +#define BMI160_REG_CMD 0x7E +#define BMI160_CMD_ACCEL_PM_SUSPEND 0x10 +#define BMI160_CMD_ACCEL_PM_NORMAL 0x11 +#define BMI160_CMD_ACCEL_PM_LOW_POWER 0x12 +#define BMI160_CMD_GYRO_PM_SUSPEND 0x14 +#define BMI160_CMD_GYRO_PM_NORMAL 0x15 +#define BMI160_CMD_GYRO_PM_FAST_STARTUP 0x17 +#define BMI160_CMD_SOFTRESET 0xB6 + +#define BMI160_REG_DUMMY 0x7F + +#define BMI160_ACCEL_PMU_MIN_USLEEP 3200 +#define BMI160_ACCEL_PMU_MAX_USLEEP 3800 +#define BMI160_GYRO_PMU_MIN_USLEEP 55000 +#define BMI160_GYRO_PMU_MAX_USLEEP 80000 +#define BMI160_SOFTRESET_USLEEP 1000 + +#define BMI160_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +/* scan indexes follow DATA register order */ +enum bmi160_scan_axis { + BMI160_SCAN_EXT_MAGN_X = 0, + BMI160_SCAN_EXT_MAGN_Y, + BMI160_SCAN_EXT_MAGN_Z, + BMI160_SCAN_RHALL, + BMI160_SCAN_GYRO_X, + BMI160_SCAN_GYRO_Y, + BMI160_SCAN_GYRO_Z, + BMI160_SCAN_ACCEL_X, + BMI160_SCAN_ACCEL_Y, + BMI160_SCAN_ACCEL_Z, + BMI160_SCAN_TIMESTAMP, +}; + +enum bmi160_sensor_type { + BMI160_ACCEL = 0, + BMI160_GYRO, + BMI160_EXT_MAGN, + BMI160_NUM_SENSORS /* must be last */ +}; + +struct bmi160_data { + struct regmap *regmap; +}; + +const struct regmap_config bmi160_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; +EXPORT_SYMBOL(bmi160_regmap_config); + +struct bmi160_regs { + u8 data; /* LSB byte register for X-axis */ + u8 config; + u8 config_odr_mask; + u8 config_bwp_mask; + u8 range; + u8 pmu_cmd_normal; + u8 pmu_cmd_suspend; +}; + +static struct bmi160_regs bmi160_regs[] = { + [BMI160_ACCEL] = { + .data = BMI160_REG_DATA_ACCEL_XOUT_L, + .config = BMI160_REG_ACCEL_CONFIG, + .config_odr_mask = BMI160_ACCEL_CONFIG_ODR_MASK, + .config_bwp_mask = BMI160_ACCEL_CONFIG_BWP_MASK, + .range = BMI160_REG_ACCEL_RANGE, + .pmu_cmd_normal = BMI160_CMD_ACCEL_PM_NORMAL, + .pmu_cmd_suspend = BMI160_CMD_ACCEL_PM_SUSPEND, + }, + [BMI160_GYRO] = { + .data = BMI160_REG_DATA_GYRO_XOUT_L, + .config = BMI160_REG_GYRO_CONFIG, + .config_odr_mask = BMI160_GYRO_CONFIG_ODR_MASK, + .config_bwp_mask = BMI160_GYRO_CONFIG_BWP_MASK, + .range = BMI160_REG_GYRO_RANGE, + .pmu_cmd_normal = BMI160_CMD_GYRO_PM_NORMAL, + .pmu_cmd_suspend = BMI160_CMD_GYRO_PM_SUSPEND, + }, +}; + +struct bmi160_pmu_time { + unsigned long min; + unsigned long max; +}; + +static struct bmi160_pmu_time bmi160_pmu_time[] = { + [BMI160_ACCEL] = { + .min = BMI160_ACCEL_PMU_MIN_USLEEP, + .max = BMI160_ACCEL_PMU_MAX_USLEEP + }, + [BMI160_GYRO] = { + .min = BMI160_GYRO_PMU_MIN_USLEEP, + .max = BMI160_GYRO_PMU_MIN_USLEEP, + }, +}; + +struct bmi160_scale { + u8 bits; + int uscale; +}; + +struct bmi160_odr { + u8 bits; + int odr; + int uodr; +}; + +static const struct bmi160_scale bmi160_accel_scale[] = { + { BMI160_ACCEL_RANGE_2G, 598}, + { BMI160_ACCEL_RANGE_4G, 1197}, + { BMI160_ACCEL_RANGE_8G, 2394}, + { BMI160_ACCEL_RANGE_16G, 4788}, +}; + +static const struct bmi160_scale bmi160_gyro_scale[] = { + { BMI160_GYRO_RANGE_2000DPS, 1065}, + { BMI160_GYRO_RANGE_1000DPS, 532}, + { BMI160_GYRO_RANGE_500DPS, 266}, + { BMI160_GYRO_RANGE_250DPS, 133}, + { BMI160_GYRO_RANGE_125DPS, 66}, +}; + +struct bmi160_scale_item { + const struct bmi160_scale *tbl; + int num; +}; + +static const struct bmi160_scale_item bmi160_scale_table[] = { + [BMI160_ACCEL] = { + .tbl = bmi160_accel_scale, + .num = ARRAY_SIZE(bmi160_accel_scale), + }, + [BMI160_GYRO] = { + .tbl = bmi160_gyro_scale, + .num = ARRAY_SIZE(bmi160_gyro_scale), + }, +}; + +static const struct bmi160_odr bmi160_accel_odr[] = { + {0x01, 0, 781250}, + {0x02, 1, 562500}, + {0x03, 3, 125000}, + {0x04, 6, 250000}, + {0x05, 12, 500000}, + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, +}; + +static const struct bmi160_odr bmi160_gyro_odr[] = { + {0x06, 25, 0}, + {0x07, 50, 0}, + {0x08, 100, 0}, + {0x09, 200, 0}, + {0x0A, 400, 0}, + {0x0B, 800, 0}, + {0x0C, 1600, 0}, + {0x0D, 3200, 0}, +}; + +struct bmi160_odr_item { + const struct bmi160_odr *tbl; + int num; +}; + +static const struct bmi160_odr_item bmi160_odr_table[] = { + [BMI160_ACCEL] = { + .tbl = bmi160_accel_odr, + .num = ARRAY_SIZE(bmi160_accel_odr), + }, + [BMI160_GYRO] = { + .tbl = bmi160_gyro_odr, + .num = ARRAY_SIZE(bmi160_gyro_odr), + }, +}; + +static const struct iio_chan_spec bmi160_channels[] = { + BMI160_CHANNEL(IIO_ACCEL, X, BMI160_SCAN_ACCEL_X), + BMI160_CHANNEL(IIO_ACCEL, Y, BMI160_SCAN_ACCEL_Y), + BMI160_CHANNEL(IIO_ACCEL, Z, BMI160_SCAN_ACCEL_Z), + BMI160_CHANNEL(IIO_ANGL_VEL, X, BMI160_SCAN_GYRO_X), + BMI160_CHANNEL(IIO_ANGL_VEL, Y, BMI160_SCAN_GYRO_Y), + BMI160_CHANNEL(IIO_ANGL_VEL, Z, BMI160_SCAN_GYRO_Z), + IIO_CHAN_SOFT_TIMESTAMP(BMI160_SCAN_TIMESTAMP), +}; + +static enum bmi160_sensor_type bmi160_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return BMI160_ACCEL; + case IIO_ANGL_VEL: + return BMI160_GYRO; + default: + return -EINVAL; + } +} + +static +int bmi160_set_mode(struct bmi160_data *data, enum bmi160_sensor_type t, + bool mode) +{ + int ret; + u8 cmd; + + if (mode) + cmd = bmi160_regs[t].pmu_cmd_normal; + else + cmd = bmi160_regs[t].pmu_cmd_suspend; + + ret = regmap_write(data->regmap, BMI160_REG_CMD, cmd); + if (ret < 0) + return ret; + + usleep_range(bmi160_pmu_time[t].min, bmi160_pmu_time[t].max); + + return 0; +} + +static +int bmi160_set_scale(struct bmi160_data *data, enum bmi160_sensor_type t, + int uscale) +{ + int i; + + for (i = 0; i < bmi160_scale_table[t].num; i++) + if (bmi160_scale_table[t].tbl[i].uscale == uscale) + break; + + if (i == bmi160_scale_table[t].num) + return -EINVAL; + + return regmap_write(data->regmap, bmi160_regs[t].range, + bmi160_scale_table[t].tbl[i].bits); +} + +static +int bmi160_get_scale(struct bmi160_data *data, enum bmi160_sensor_type t, + int *uscale) +{ + int i, ret, val; + + ret = regmap_read(data->regmap, bmi160_regs[t].range, &val); + if (ret < 0) + return ret; + + for (i = 0; i < bmi160_scale_table[t].num; i++) + if (bmi160_scale_table[t].tbl[i].bits == val) { + *uscale = bmi160_scale_table[t].tbl[i].uscale; + return 0; + } + + return -EINVAL; +} + +static int bmi160_get_data(struct bmi160_data *data, int chan_type, + int axis, int *val) +{ + u8 reg; + int ret; + __le16 sample; + enum bmi160_sensor_type t = bmi160_to_sensor(chan_type); + + reg = bmi160_regs[t].data + (axis - IIO_MOD_X) * sizeof(__le16); + + ret = regmap_bulk_read(data->regmap, reg, &sample, sizeof(__le16)); + if (ret < 0) + return ret; + + *val = sign_extend32(le16_to_cpu(sample), 15); + + return 0; +} + +static +int bmi160_set_odr(struct bmi160_data *data, enum bmi160_sensor_type t, + int odr, int uodr) +{ + int i; + + for (i = 0; i < bmi160_odr_table[t].num; i++) + if (bmi160_odr_table[t].tbl[i].odr == odr && + bmi160_odr_table[t].tbl[i].uodr == uodr) + break; + + if (i >= bmi160_odr_table[t].num) + return -EINVAL; + + return regmap_update_bits(data->regmap, + bmi160_regs[t].config, + bmi160_regs[t].config_odr_mask, + bmi160_odr_table[t].tbl[i].bits); +} + +static int bmi160_get_odr(struct bmi160_data *data, enum bmi160_sensor_type t, + int *odr, int *uodr) +{ + int i, val, ret; + + ret = regmap_read(data->regmap, bmi160_regs[t].config, &val); + if (ret < 0) + return ret; + + val &= bmi160_regs[t].config_odr_mask; + + for (i = 0; i < bmi160_odr_table[t].num; i++) + if (val == bmi160_odr_table[t].tbl[i].bits) + break; + + if (i >= bmi160_odr_table[t].num) + return -EINVAL; + + *odr = bmi160_odr_table[t].tbl[i].odr; + *uodr = bmi160_odr_table[t].tbl[i].uodr; + + return 0; +} + +static irqreturn_t bmi160_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi160_data *data = iio_priv(indio_dev); + s16 buf[16]; /* 3 sens x 3 axis x s16 + 3 x s16 pad + 4 x s16 tstamp */ + int i, ret, j = 0, base = BMI160_REG_DATA_MAGN_XOUT_L; + __le16 sample; + + for_each_set_bit(i, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = regmap_bulk_read(data->regmap, base + i * sizeof(__le16), + &sample, sizeof(__le16)); + if (ret < 0) + goto done; + buf[j++] = sample; + } + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int bmi160_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + struct bmi160_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = bmi160_get_data(data, chan->type, chan->channel2, val); + if (ret < 0) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = bmi160_get_scale(data, + bmi160_to_sensor(chan->type), val2); + return ret < 0 ? ret : IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = bmi160_get_odr(data, bmi160_to_sensor(chan->type), + val, val2); + return ret < 0 ? ret : IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + + return 0; +} + +static int bmi160_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bmi160_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bmi160_set_scale(data, + bmi160_to_sensor(chan->type), val2); + break; + case IIO_CHAN_INFO_SAMP_FREQ: + return bmi160_set_odr(data, bmi160_to_sensor(chan->type), + val, val2); + default: + return -EINVAL; + } + + return 0; +} + +static const struct iio_info bmi160_info = { + .driver_module = THIS_MODULE, + .read_raw = bmi160_read_raw, + .write_raw = bmi160_write_raw, +}; + +static const char *bmi160_match_acpi_device(struct device *dev) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + return dev_name(dev); +} + +static int bmi160_chip_init(struct bmi160_data *data, bool use_spi) +{ + int ret; + unsigned int val; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_write(data->regmap, BMI160_REG_CMD, BMI160_CMD_SOFTRESET); + if (ret < 0) + return ret; + + usleep_range(BMI160_SOFTRESET_USLEEP, BMI160_SOFTRESET_USLEEP + 1); + + /* + * CS rising edge is needed before starting SPI, so do a dummy read + * See Section 3.2.1, page 86 of the datasheet + */ + if (use_spi) { + ret = regmap_read(data->regmap, BMI160_REG_DUMMY, &val); + if (ret < 0) + return ret; + } + + ret = regmap_read(data->regmap, BMI160_REG_CHIP_ID, &val); + if (ret < 0) { + dev_err(dev, "Error reading chip id\n"); + return ret; + } + if (val != BMI160_CHIP_ID_VAL) { + dev_err(dev, "Wrong chip id, got %x expected %x\n", + val, BMI160_CHIP_ID_VAL); + return -ENODEV; + } + + ret = bmi160_set_mode(data, BMI160_ACCEL, true); + if (ret < 0) + return ret; + + ret = bmi160_set_mode(data, BMI160_GYRO, true); + if (ret < 0) + return ret; + + return 0; +} + +static void bmi160_chip_uninit(struct bmi160_data *data) +{ + bmi160_set_mode(data, BMI160_GYRO, false); + bmi160_set_mode(data, BMI160_ACCEL, false); +} + +int bmi160_core_probe(struct device *dev, struct regmap *regmap, + const char *name, bool use_spi) +{ + struct iio_dev *indio_dev; + struct bmi160_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + + ret = bmi160_chip_init(data, use_spi); + if (ret < 0) + return ret; + + if (!name && ACPI_HANDLE(dev)) + name = bmi160_match_acpi_device(dev); + + indio_dev->dev.parent = dev; + indio_dev->channels = bmi160_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi160_channels); + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &bmi160_info; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + bmi160_trigger_handler, NULL); + if (ret < 0) + goto uninit; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +uninit: + bmi160_chip_uninit(data); + return ret; +} +EXPORT_SYMBOL_GPL(bmi160_core_probe); + +void bmi160_core_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bmi160_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + bmi160_chip_uninit(data); +} +EXPORT_SYMBOL_GPL(bmi160_core_remove); + +MODULE_AUTHOR("Daniel Baluta +#include +#include +#include + +#include "bmi160.h" + +static int bmi160_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(client, &bmi160_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + + return bmi160_core_probe(&client->dev, regmap, name, false); +} + +static int bmi160_i2c_remove(struct i2c_client *client) +{ + bmi160_core_remove(&client->dev); + + return 0; +} + +static const struct i2c_device_id bmi160_i2c_id[] = { + {"bmi160", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, bmi160_i2c_id); + +static const struct acpi_device_id bmi160_acpi_match[] = { + {"BMI0160", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); + +static struct i2c_driver bmi160_i2c_driver = { + .driver = { + .name = "bmi160_i2c", + .acpi_match_table = ACPI_PTR(bmi160_acpi_match), + }, + .probe = bmi160_i2c_probe, + .remove = bmi160_i2c_remove, + .id_table = bmi160_i2c_id, +}; +module_i2c_driver(bmi160_i2c_driver); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("BMI160 I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/imu/bmi160/bmi160_spi.c b/drivers/iio/imu/bmi160/bmi160_spi.c new file mode 100644 index 0000000..1ec8b12 --- /dev/null +++ b/drivers/iio/imu/bmi160/bmi160_spi.c @@ -0,0 +1,63 @@ +/* + * BMI160 - Bosch IMU, SPI bits + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ +#include +#include +#include +#include + +#include "bmi160.h" + +static int bmi160_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &bmi160_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + return bmi160_core_probe(&spi->dev, regmap, id->name, true); +} + +static int bmi160_spi_remove(struct spi_device *spi) +{ + bmi160_core_remove(&spi->dev); + + return 0; +} + +static const struct spi_device_id bmi160_spi_id[] = { + {"bmi160", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, bmi160_spi_id); + +static const struct acpi_device_id bmi160_acpi_match[] = { + {"BMI0160", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, bmi160_acpi_match); + +static struct spi_driver bmi160_spi_driver = { + .probe = bmi160_spi_probe, + .remove = bmi160_spi_remove, + .id_table = bmi160_spi_id, + .driver = { + .acpi_match_table = ACPI_PTR(bmi160_acpi_match), + .name = "bmi160_spi", + }, +}; +module_spi_driver(bmi160_spi_driver); + +MODULE_AUTHOR("Daniel Baluta handle, "CNF0", NULL, &buffer); if (ACPI_FAILURE(status)) @@ -66,11 +67,11 @@ static int asus_acpi_get_sensor_info(struct acpi_device *adev, union acpi_object *elem; int j; - elem = &(cpm->package.elements[i]); + elem = &cpm->package.elements[i]; for (j = 0; j < elem->package.count; ++j) { union acpi_object *sub_elem; - sub_elem = &(elem->package.elements[j]); + sub_elem = &elem->package.elements[j]; if (sub_elem->type == ACPI_TYPE_STRING) strlcpy(info->type, sub_elem->string.pointer, sizeof(info->type)); @@ -82,10 +83,10 @@ static int asus_acpi_get_sensor_info(struct acpi_device *adev, } } } - + ret = cpm->package.count; kfree(buffer.pointer); - return cpm->package.count; + return ret; } static int acpi_i2c_check_resource(struct acpi_resource *ares, void *data) @@ -139,22 +140,23 @@ static int inv_mpu_process_acpi_config(struct i2c_client *client, return 0; } -int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) { + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); st->mux_client = NULL; - if (ACPI_HANDLE(&st->client->dev)) { + if (ACPI_HANDLE(&client->dev)) { struct i2c_board_info info; struct acpi_device *adev; int ret = -1; - adev = ACPI_COMPANION(&st->client->dev); + adev = ACPI_COMPANION(&client->dev); memset(&info, 0, sizeof(info)); dmi_check_system(inv_mpu_dev_list); switch (matched_product_name) { case INV_MPU_ASUS_T100TA: - ret = asus_acpi_get_sensor_info(adev, st->client, + ret = asus_acpi_get_sensor_info(adev, client, &info); break; /* Add more matched product processing here */ @@ -166,7 +168,7 @@ int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) /* No matching DMI, so create device on INV6XX type */ unsigned short primary, secondary; - ret = inv_mpu_process_acpi_config(st->client, &primary, + ret = inv_mpu_process_acpi_config(client, &primary, &secondary); if (!ret && secondary) { char *name; @@ -182,17 +184,18 @@ int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) } else return 0; /* no secondary addr, which is OK */ } - st->mux_client = i2c_new_device(st->mux_adapter, &info); + st->mux_client = i2c_new_device(st->muxc->adapter[0], &info); if (!st->mux_client) return -ENODEV; - } return 0; } -void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st) +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) { + struct inv_mpu6050_state *st = iio_priv(dev_get_drvdata(&client->dev)); + if (st->mux_client) i2c_unregister_device(st->mux_client); } @@ -200,12 +203,12 @@ void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st) #include "inv_mpu_iio.h" -int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st) +int inv_mpu_acpi_create_mux_client(struct i2c_client *client) { return 0; } -void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st) +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client) { } #endif diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c index f0e0609..ee40dae 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "inv_mpu_iio.h" @@ -39,6 +38,26 @@ static const int gyro_scale_6050[] = {133090, 266181, 532362, 1064724}; */ static const int accel_scale[] = {598, 1196, 2392, 4785}; +static const struct inv_mpu6050_reg_map reg_set_6500 = { + .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, + .lpf = INV_MPU6050_REG_CONFIG, + .user_ctrl = INV_MPU6050_REG_USER_CTRL, + .fifo_en = INV_MPU6050_REG_FIFO_EN, + .gyro_config = INV_MPU6050_REG_GYRO_CONFIG, + .accl_config = INV_MPU6050_REG_ACCEL_CONFIG, + .fifo_count_h = INV_MPU6050_REG_FIFO_COUNT_H, + .fifo_r_w = INV_MPU6050_REG_FIFO_R_W, + .raw_gyro = INV_MPU6050_REG_RAW_GYRO, + .raw_accl = INV_MPU6050_REG_RAW_ACCEL, + .temperature = INV_MPU6050_REG_TEMPERATURE, + .int_enable = INV_MPU6050_REG_INT_ENABLE, + .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, + .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, + .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6500_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, +}; + static const struct inv_mpu6050_reg_map reg_set_6050 = { .sample_rate_div = INV_MPU6050_REG_SAMPLE_RATE_DIV, .lpf = INV_MPU6050_REG_CONFIG, @@ -55,6 +74,8 @@ static const struct inv_mpu6050_reg_map reg_set_6050 = { .pwr_mgmt_1 = INV_MPU6050_REG_PWR_MGMT_1, .pwr_mgmt_2 = INV_MPU6050_REG_PWR_MGMT_2, .int_pin_cfg = INV_MPU6050_REG_INT_PIN_CFG, + .accl_offset = INV_MPU6050_REG_ACCEL_OFFSET, + .gyro_offset = INV_MPU6050_REG_GYRO_OFFSET, }; static const struct inv_mpu6050_chip_config chip_config_6050 = { @@ -66,143 +87,81 @@ static const struct inv_mpu6050_chip_config chip_config_6050 = { .accl_fs = INV_MPU6050_FS_02G, }; -static const struct inv_mpu6050_hw hw_info[INV_NUM_PARTS] = { +/* Indexed by enum inv_devices */ +static const struct inv_mpu6050_hw hw_info[] = { { - .num_reg = 117, + .whoami = INV_MPU6050_WHOAMI_VALUE, .name = "MPU6050", .reg = ®_set_6050, .config = &chip_config_6050, }, + { + .whoami = INV_MPU6500_WHOAMI_VALUE, + .name = "MPU6500", + .reg = ®_set_6500, + .config = &chip_config_6050, + }, + { + .whoami = INV_MPU6000_WHOAMI_VALUE, + .name = "MPU6000", + .reg = ®_set_6050, + .config = &chip_config_6050, + }, + { + .whoami = INV_MPU9150_WHOAMI_VALUE, + .name = "MPU9150", + .reg = ®_set_6050, + .config = &chip_config_6050, + }, }; -int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 d) -{ - return i2c_smbus_write_i2c_block_data(st->client, reg, 1, &d); -} - -/* - * The i2c read/write needs to happen in unlocked mode. As the parent - * adapter is common. If we use locked versions, it will fail as - * the mux adapter will lock the parent i2c adapter, while calling - * select/deselect functions. - */ -static int inv_mpu6050_write_reg_unlocked(struct inv_mpu6050_state *st, - u8 reg, u8 d) -{ - int ret; - u8 buf[2]; - struct i2c_msg msg[1] = { - { - .addr = st->client->addr, - .flags = 0, - .len = sizeof(buf), - .buf = buf, - } - }; - - buf[0] = reg; - buf[1] = d; - ret = __i2c_transfer(st->client->adapter, msg, 1); - if (ret != 1) - return ret; - - return 0; -} - -static int inv_mpu6050_select_bypass(struct i2c_adapter *adap, void *mux_priv, - u32 chan_id) -{ - struct iio_dev *indio_dev = mux_priv; - struct inv_mpu6050_state *st = iio_priv(indio_dev); - int ret = 0; - - /* Use the same mutex which was used everywhere to protect power-op */ - mutex_lock(&indio_dev->mlock); - if (!st->powerup_count) { - ret = inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1, - 0); - if (ret) - goto write_error; - - msleep(INV_MPU6050_REG_UP_TIME); - } - if (!ret) { - st->powerup_count++; - ret = inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg, - st->client->irq | - INV_MPU6050_BIT_BYPASS_EN); - } -write_error: - mutex_unlock(&indio_dev->mlock); - - return ret; -} - -static int inv_mpu6050_deselect_bypass(struct i2c_adapter *adap, - void *mux_priv, u32 chan_id) -{ - struct iio_dev *indio_dev = mux_priv; - struct inv_mpu6050_state *st = iio_priv(indio_dev); - - mutex_lock(&indio_dev->mlock); - /* It doesn't really mattter, if any of the calls fails */ - inv_mpu6050_write_reg_unlocked(st, st->reg->int_pin_cfg, - st->client->irq); - st->powerup_count--; - if (!st->powerup_count) - inv_mpu6050_write_reg_unlocked(st, st->reg->pwr_mgmt_1, - INV_MPU6050_BIT_SLEEP); - mutex_unlock(&indio_dev->mlock); - - return 0; -} - int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask) { - u8 d, mgmt_1; + unsigned int d, mgmt_1; int result; - - /* switch clock needs to be careful. Only when gyro is on, can - clock source be switched to gyro. Otherwise, it must be set to - internal clock */ - if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { - result = i2c_smbus_read_i2c_block_data(st->client, - st->reg->pwr_mgmt_1, 1, &mgmt_1); - if (result != 1) + /* + * switch clock needs to be careful. Only when gyro is on, can + * clock source be switched to gyro. Otherwise, it must be set to + * internal clock + */ + if (mask == INV_MPU6050_BIT_PWR_GYRO_STBY) { + result = regmap_read(st->map, st->reg->pwr_mgmt_1, &mgmt_1); + if (result) return result; mgmt_1 &= ~INV_MPU6050_BIT_CLK_MASK; } - if ((INV_MPU6050_BIT_PWR_GYRO_STBY == mask) && (!en)) { - /* turning off gyro requires switch to internal clock first. - Then turn off gyro engine */ + if ((mask == INV_MPU6050_BIT_PWR_GYRO_STBY) && (!en)) { + /* + * turning off gyro requires switch to internal clock first. + * Then turn off gyro engine + */ mgmt_1 |= INV_CLK_INTERNAL; - result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, mgmt_1); + result = regmap_write(st->map, st->reg->pwr_mgmt_1, mgmt_1); if (result) return result; } - result = i2c_smbus_read_i2c_block_data(st->client, - st->reg->pwr_mgmt_2, 1, &d); - if (result != 1) + result = regmap_read(st->map, st->reg->pwr_mgmt_2, &d); + if (result) return result; if (en) d &= ~mask; else d |= mask; - result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_2, d); + result = regmap_write(st->map, st->reg->pwr_mgmt_2, d); if (result) return result; if (en) { /* Wait for output stabilize */ msleep(INV_MPU6050_TEMP_UP_TIME); - if (INV_MPU6050_BIT_PWR_GYRO_STBY == mask) { + if (mask == INV_MPU6050_BIT_PWR_GYRO_STBY) { /* switch internal clock to PLL */ mgmt_1 |= INV_CLK_PLL; - result = inv_mpu6050_write_reg(st, - st->reg->pwr_mgmt_1, mgmt_1); + result = regmap_write(st->map, + st->reg->pwr_mgmt_1, mgmt_1); if (result) return result; } @@ -218,25 +177,26 @@ int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on) if (power_on) { /* Already under indio-dev->mlock mutex */ if (!st->powerup_count) - result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, - 0); + result = regmap_write(st->map, st->reg->pwr_mgmt_1, 0); if (!result) st->powerup_count++; } else { st->powerup_count--; if (!st->powerup_count) - result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, - INV_MPU6050_BIT_SLEEP); + result = regmap_write(st->map, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_SLEEP); } if (result) return result; if (power_on) - msleep(INV_MPU6050_REG_UP_TIME); + usleep_range(INV_MPU6050_REG_UP_TIME_MIN, + INV_MPU6050_REG_UP_TIME_MAX); return 0; } +EXPORT_SYMBOL_GPL(inv_mpu6050_set_power_itg); /** * inv_mpu6050_init_config() - Initialize hardware, disable FIFO. @@ -257,59 +217,73 @@ static int inv_mpu6050_init_config(struct iio_dev *indio_dev) if (result) return result; d = (INV_MPU6050_FSR_2000DPS << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, st->reg->gyro_config, d); + result = regmap_write(st->map, st->reg->gyro_config, d); if (result) return result; d = INV_MPU6050_FILTER_20HZ; - result = inv_mpu6050_write_reg(st, st->reg->lpf, d); + result = regmap_write(st->map, st->reg->lpf, d); if (result) return result; d = INV_MPU6050_ONE_K_HZ / INV_MPU6050_INIT_FIFO_RATE - 1; - result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + result = regmap_write(st->map, st->reg->sample_rate_div, d); if (result) return result; d = (INV_MPU6050_FS_02G << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, st->reg->accl_config, d); + result = regmap_write(st->map, st->reg->accl_config, d); if (result) return result; memcpy(&st->chip_config, hw_info[st->chip_type].config, - sizeof(struct inv_mpu6050_chip_config)); + sizeof(struct inv_mpu6050_chip_config)); result = inv_mpu6050_set_power_itg(st, false); return result; } +static int inv_mpu6050_sensor_set(struct inv_mpu6050_state *st, int reg, + int axis, int val) +{ + int ind, result; + __be16 d = cpu_to_be16(val); + + ind = (axis - IIO_MOD_X) * 2; + result = regmap_bulk_write(st->map, reg + ind, (u8 *)&d, 2); + if (result) + return -EINVAL; + + return 0; +} + static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg, - int axis, int *val) + int axis, int *val) { int ind, result; __be16 d; ind = (axis - IIO_MOD_X) * 2; - result = i2c_smbus_read_i2c_block_data(st->client, reg + ind, 2, - (u8 *)&d); - if (result != 2) + result = regmap_bulk_read(st->map, reg + ind, (u8 *)&d, 2); + if (result) return -EINVAL; *val = (short)be16_to_cpup(&d); return IIO_VAL_INT; } -static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, - int *val2, - long mask) { +static int +inv_mpu6050_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; switch (mask) { case IIO_CHAN_INFO_RAW: { - int ret, result; + int result; ret = IIO_VAL_INT; result = 0; @@ -323,16 +297,16 @@ static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, switch (chan->type) { case IIO_ANGL_VEL: if (!st->chip_config.gyro_fifo_enable || - !st->chip_config.enable) { + !st->chip_config.enable) { result = inv_mpu6050_switch_engine(st, true, INV_MPU6050_BIT_PWR_GYRO_STBY); if (result) goto error_read_raw; } - ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, - chan->channel2, val); + ret = inv_mpu6050_sensor_show(st, st->reg->raw_gyro, + chan->channel2, val); if (!st->chip_config.gyro_fifo_enable || - !st->chip_config.enable) { + !st->chip_config.enable) { result = inv_mpu6050_switch_engine(st, false, INV_MPU6050_BIT_PWR_GYRO_STBY); if (result) @@ -341,16 +315,16 @@ static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, break; case IIO_ACCEL: if (!st->chip_config.accl_fifo_enable || - !st->chip_config.enable) { + !st->chip_config.enable) { result = inv_mpu6050_switch_engine(st, true, INV_MPU6050_BIT_PWR_ACCL_STBY); if (result) goto error_read_raw; } ret = inv_mpu6050_sensor_show(st, st->reg->raw_accl, - chan->channel2, val); + chan->channel2, val); if (!st->chip_config.accl_fifo_enable || - !st->chip_config.enable) { + !st->chip_config.enable) { result = inv_mpu6050_switch_engine(st, false, INV_MPU6050_BIT_PWR_ACCL_STBY); if (result) @@ -360,8 +334,8 @@ static int inv_mpu6050_read_raw(struct iio_dev *indio_dev, case IIO_TEMP: /* wait for stablization */ msleep(INV_MPU6050_SENSOR_UP_TIME); - inv_mpu6050_sensor_show(st, st->reg->temperature, - IIO_MOD_X, val); + ret = inv_mpu6050_sensor_show(st, st->reg->temperature, + IIO_MOD_X, val); break; default: ret = -EINVAL; @@ -405,6 +379,20 @@ error_read_raw: default: return -EINVAL; } + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + ret = inv_mpu6050_sensor_show(st, st->reg->gyro_offset, + chan->channel2, val); + return IIO_VAL_INT; + case IIO_ACCEL: + ret = inv_mpu6050_sensor_show(st, st->reg->accl_offset, + chan->channel2, val); + return IIO_VAL_INT; + + default: + return -EINVAL; + } default: return -EINVAL; } @@ -418,8 +406,7 @@ static int inv_mpu6050_write_gyro_scale(struct inv_mpu6050_state *st, int val) for (i = 0; i < ARRAY_SIZE(gyro_scale_6050); ++i) { if (gyro_scale_6050[i] == val) { d = (i << INV_MPU6050_GYRO_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, - st->reg->gyro_config, d); + result = regmap_write(st->map, st->reg->gyro_config, d); if (result) return result; @@ -448,6 +435,7 @@ static int inv_write_raw_get_fmt(struct iio_dev *indio_dev, return -EINVAL; } + static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val) { int result, i; @@ -456,8 +444,7 @@ static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val) for (i = 0; i < ARRAY_SIZE(accel_scale); ++i) { if (accel_scale[i] == val) { d = (i << INV_MPU6050_ACCL_CONFIG_FSR_SHIFT); - result = inv_mpu6050_write_reg(st, - st->reg->accl_config, d); + result = regmap_write(st->map, st->reg->accl_config, d); if (result) return result; @@ -470,16 +457,17 @@ static int inv_mpu6050_write_accel_scale(struct inv_mpu6050_state *st, int val) } static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, - int val2, - long mask) { + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ struct inv_mpu6050_state *st = iio_priv(indio_dev); int result; mutex_lock(&indio_dev->mlock); - /* we should only update scale when the chip is disabled, i.e., - not running */ + /* + * we should only update scale when the chip is disabled, i.e. + * not running + */ if (st->chip_config.enable) { result = -EBUSY; goto error_write_raw; @@ -502,6 +490,21 @@ static int inv_mpu6050_write_raw(struct iio_dev *indio_dev, break; } break; + case IIO_CHAN_INFO_CALIBBIAS: + switch (chan->type) { + case IIO_ANGL_VEL: + result = inv_mpu6050_sensor_set(st, + st->reg->gyro_offset, + chan->channel2, val); + break; + case IIO_ACCEL: + result = inv_mpu6050_sensor_set(st, + st->reg->accl_offset, + chan->channel2, val); + break; + default: + result = -EINVAL; + } default: result = -EINVAL; break; @@ -537,7 +540,7 @@ static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) i++; data = d[i]; - result = inv_mpu6050_write_reg(st, st->reg->lpf, data); + result = regmap_write(st->map, st->reg->lpf, data); if (result) return result; st->chip_config.lpf = data; @@ -548,8 +551,9 @@ static int inv_mpu6050_set_lpf(struct inv_mpu6050_state *st, int rate) /** * inv_mpu6050_fifo_rate_store() - Set fifo rate. */ -static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t count) +static ssize_t +inv_mpu6050_fifo_rate_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { s32 fifo_rate; u8 d; @@ -560,7 +564,7 @@ static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, if (kstrtoint(buf, 10, &fifo_rate)) return -EINVAL; if (fifo_rate < INV_MPU6050_MIN_FIFO_RATE || - fifo_rate > INV_MPU6050_MAX_FIFO_RATE) + fifo_rate > INV_MPU6050_MAX_FIFO_RATE) return -EINVAL; if (fifo_rate == st->chip_config.fifo_rate) return count; @@ -575,7 +579,7 @@ static ssize_t inv_mpu6050_fifo_rate_store(struct device *dev, goto fifo_rate_fail; d = INV_MPU6050_ONE_K_HZ / fifo_rate - 1; - result = inv_mpu6050_write_reg(st, st->reg->sample_rate_div, d); + result = regmap_write(st->map, st->reg->sample_rate_div, d); if (result) goto fifo_rate_fail; st->chip_config.fifo_rate = fifo_rate; @@ -596,8 +600,9 @@ fifo_rate_fail: /** * inv_fifo_rate_show() - Get the current sampling rate. */ -static ssize_t inv_fifo_rate_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t +inv_fifo_rate_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); @@ -607,17 +612,23 @@ static ssize_t inv_fifo_rate_show(struct device *dev, /** * inv_attr_show() - calling this function will show current * parameters. + * + * Deprecated in favor of IIO mounting matrix API. + * + * See inv_get_mount_matrix() */ -static ssize_t inv_attr_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t inv_attr_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct inv_mpu6050_state *st = iio_priv(dev_to_iio_dev(dev)); struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); s8 *m; switch (this_attr->address) { - /* In MPU6050, the two matrix are the same because gyro and accel - are integrated in one chip */ + /* + * In MPU6050, the two matrix are the same because gyro and accel + * are integrated in one chip + */ case ATTR_GYRO_MATRIX: case ATTR_ACCL_MATRIX: m = st->plat_data.orientation; @@ -649,21 +660,35 @@ static int inv_mpu6050_validate_trigger(struct iio_dev *indio_dev, return 0; } +static const struct iio_mount_matrix * +inv_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + return &((struct inv_mpu6050_state *)iio_priv(indio_dev))->orientation; +} + +static const struct iio_chan_spec_ext_info inv_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, inv_get_mount_matrix), + { }, +}; + #define INV_MPU6050_CHAN(_type, _channel2, _index) \ { \ .type = _type, \ .modified = 1, \ .channel2 = _channel2, \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ .scan_index = _index, \ .scan_type = { \ .sign = 's', \ .realbits = 16, \ .storagebits = 16, \ - .shift = 0 , \ + .shift = 0, \ .endianness = IIO_BE, \ }, \ + .ext_info = inv_ext_info, \ } static const struct iio_chan_spec inv_mpu_channels[] = { @@ -674,7 +699,7 @@ static const struct iio_chan_spec inv_mpu_channels[] = { */ { .type = IIO_TEMP, - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE), .scan_index = -1, @@ -696,14 +721,16 @@ static IIO_CONST_ATTR(in_accel_scale_available, "0.000598 0.001196 0.002392 0.004785"); static IIO_DEV_ATTR_SAMP_FREQ(S_IRUGO | S_IWUSR, inv_fifo_rate_show, inv_mpu6050_fifo_rate_store); + +/* Deprecated: kept for userspace backward compatibility. */ static IIO_DEVICE_ATTR(in_gyro_matrix, S_IRUGO, inv_attr_show, NULL, ATTR_GYRO_MATRIX); static IIO_DEVICE_ATTR(in_accel_matrix, S_IRUGO, inv_attr_show, NULL, ATTR_ACCL_MATRIX); static struct attribute *inv_attributes[] = { - &iio_dev_attr_in_gyro_matrix.dev_attr.attr, - &iio_dev_attr_in_accel_matrix.dev_attr.attr, + &iio_dev_attr_in_gyro_matrix.dev_attr.attr, /* deprecated */ + &iio_dev_attr_in_accel_matrix.dev_attr.attr, /* deprecated */ &iio_dev_attr_sampling_frequency.dev_attr.attr, &iio_const_attr_sampling_frequency_available.dev_attr.attr, &iio_const_attr_in_accel_scale_available.dev_attr.attr, @@ -727,25 +754,37 @@ static const struct iio_info mpu_info = { /** * inv_check_and_setup_chip() - check and setup chip. */ -static int inv_check_and_setup_chip(struct inv_mpu6050_state *st, - const struct i2c_device_id *id) +static int inv_check_and_setup_chip(struct inv_mpu6050_state *st) { int result; + unsigned int regval; - st->chip_type = INV_MPU6050; st->hw = &hw_info[st->chip_type]; st->reg = hw_info[st->chip_type].reg; /* reset to make sure previous state are not there */ - result = inv_mpu6050_write_reg(st, st->reg->pwr_mgmt_1, - INV_MPU6050_BIT_H_RESET); + result = regmap_write(st->map, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_H_RESET); if (result) return result; msleep(INV_MPU6050_POWER_UP_TIME); - /* toggle power state. After reset, the sleep bit could be on - or off depending on the OTP settings. Toggling power would - make it in a definite state as well as making the hardware - state align with the software state */ + + /* check chip self-identification */ + result = regmap_read(st->map, INV_MPU6050_REG_WHOAMI, ®val); + if (result) + return result; + if (regval != st->hw->whoami) { + dev_warn(regmap_get_device(st->map), + "whoami mismatch got %#02x expected %#02hhx for %s\n", + regval, st->hw->whoami, st->hw->name); + } + + /* + * toggle power state. After reset, the sleep bit could be on + * or off depending on the OTP settings. Toggling power would + * make it in a definite state as well as making the hardware + * state align with the software state + */ result = inv_mpu6050_set_power_itg(st, false); if (result) return result; @@ -754,65 +793,76 @@ static int inv_check_and_setup_chip(struct inv_mpu6050_state *st, return result; result = inv_mpu6050_switch_engine(st, false, - INV_MPU6050_BIT_PWR_ACCL_STBY); + INV_MPU6050_BIT_PWR_ACCL_STBY); if (result) return result; result = inv_mpu6050_switch_engine(st, false, - INV_MPU6050_BIT_PWR_GYRO_STBY); + INV_MPU6050_BIT_PWR_GYRO_STBY); if (result) return result; return 0; } -/** - * inv_mpu_probe() - probe function. - * @client: i2c client. - * @id: i2c device id. - * - * Returns 0 on success, a negative error code otherwise. - */ -static int inv_mpu_probe(struct i2c_client *client, - const struct i2c_device_id *id) +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type) { struct inv_mpu6050_state *st; struct iio_dev *indio_dev; struct inv_mpu6050_platform_data *pdata; + struct device *dev = regmap_get_device(regmap); int result; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_I2C_BLOCK)) - return -ENOSYS; - - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; + BUILD_BUG_ON(ARRAY_SIZE(hw_info) != INV_NUM_PARTS); + if (chip_type < 0 || chip_type >= INV_NUM_PARTS) { + dev_err(dev, "Bad invensense chip_type=%d name=%s\n", + chip_type, name); + return -ENODEV; + } st = iio_priv(indio_dev); - st->client = client; + st->chip_type = chip_type; st->powerup_count = 0; - pdata = dev_get_platdata(&client->dev); - if (pdata) + st->irq = irq; + st->map = regmap; + + pdata = dev_get_platdata(dev); + if (!pdata) { + result = of_iio_read_mount_matrix(dev, "mount-matrix", + &st->orientation); + if (result) { + dev_err(dev, "Failed to retrieve mounting matrix %d\n", + result); + return result; + } + } else { st->plat_data = *pdata; + } + /* power is turned on inside check chip type*/ - result = inv_check_and_setup_chip(st, id); + result = inv_check_and_setup_chip(st); if (result) return result; + if (inv_mpu_bus_setup) + inv_mpu_bus_setup(indio_dev); + result = inv_mpu6050_init_config(indio_dev); if (result) { - dev_err(&client->dev, - "Could not initialize device.\n"); + dev_err(dev, "Could not initialize device.\n"); return result; } - i2c_set_clientdata(client, indio_dev); - indio_dev->dev.parent = &client->dev; - /* id will be NULL when enumerated via ACPI */ - if (id) - indio_dev->name = (char *)id->name; + dev_set_drvdata(dev, indio_dev); + indio_dev->dev.parent = dev; + /* name will be NULL when enumerated via ACPI */ + if (name) + indio_dev->name = name; else - indio_dev->name = (char *)dev_name(&client->dev); + indio_dev->name = dev_name(dev); indio_dev->channels = inv_mpu_channels; indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); @@ -824,13 +874,12 @@ static int inv_mpu_probe(struct i2c_client *client, inv_mpu6050_read_fifo, NULL); if (result) { - dev_err(&st->client->dev, "configure buffer fail %d\n", - result); + dev_err(dev, "configure buffer fail %d\n", result); return result; } result = inv_mpu6050_probe_trigger(indio_dev); if (result) { - dev_err(&st->client->dev, "trigger probe fail %d\n", result); + dev_err(dev, "trigger probe fail %d\n", result); goto out_unreg_ring; } @@ -838,102 +887,47 @@ static int inv_mpu_probe(struct i2c_client *client, spin_lock_init(&st->time_stamp_lock); result = iio_device_register(indio_dev); if (result) { - dev_err(&st->client->dev, "IIO register fail %d\n", result); + dev_err(dev, "IIO register fail %d\n", result); goto out_remove_trigger; } - st->mux_adapter = i2c_add_mux_adapter(client->adapter, - &client->dev, - indio_dev, - 0, 0, 0, - inv_mpu6050_select_bypass, - inv_mpu6050_deselect_bypass); - if (!st->mux_adapter) { - result = -ENODEV; - goto out_unreg_device; - } - - result = inv_mpu_acpi_create_mux_client(st); - if (result) - goto out_del_mux; - return 0; -out_del_mux: - i2c_del_mux_adapter(st->mux_adapter); -out_unreg_device: - iio_device_unregister(indio_dev); out_remove_trigger: inv_mpu6050_remove_trigger(st); out_unreg_ring: iio_triggered_buffer_cleanup(indio_dev); return result; } +EXPORT_SYMBOL_GPL(inv_mpu_core_probe); -static int inv_mpu_remove(struct i2c_client *client) +int inv_mpu_core_remove(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(client); - struct inv_mpu6050_state *st = iio_priv(indio_dev); + struct iio_dev *indio_dev = dev_get_drvdata(dev); - inv_mpu_acpi_delete_mux_client(st); - i2c_del_mux_adapter(st->mux_adapter); iio_device_unregister(indio_dev); - inv_mpu6050_remove_trigger(st); + inv_mpu6050_remove_trigger(iio_priv(indio_dev)); iio_triggered_buffer_cleanup(indio_dev); return 0; } +EXPORT_SYMBOL_GPL(inv_mpu_core_remove); + #ifdef CONFIG_PM_SLEEP static int inv_mpu_resume(struct device *dev) { - return inv_mpu6050_set_power_itg( - iio_priv(i2c_get_clientdata(to_i2c_client(dev))), true); + return inv_mpu6050_set_power_itg(iio_priv(dev_get_drvdata(dev)), true); } static int inv_mpu_suspend(struct device *dev) { - return inv_mpu6050_set_power_itg( - iio_priv(i2c_get_clientdata(to_i2c_client(dev))), false); + return inv_mpu6050_set_power_itg(iio_priv(dev_get_drvdata(dev)), false); } -static SIMPLE_DEV_PM_OPS(inv_mpu_pmops, inv_mpu_suspend, inv_mpu_resume); - -#define INV_MPU6050_PMOPS (&inv_mpu_pmops) -#else -#define INV_MPU6050_PMOPS NULL #endif /* CONFIG_PM_SLEEP */ -/* - * device id table is used to identify what device can be - * supported by this driver - */ -static const struct i2c_device_id inv_mpu_id[] = { - {"mpu6050", INV_MPU6050}, - {"mpu6500", INV_MPU6500}, - {} -}; - -MODULE_DEVICE_TABLE(i2c, inv_mpu_id); - -static const struct acpi_device_id inv_acpi_match[] = { - {"INVN6500", 0}, - { }, -}; - -MODULE_DEVICE_TABLE(acpi, inv_acpi_match); - -static struct i2c_driver inv_mpu_driver = { - .probe = inv_mpu_probe, - .remove = inv_mpu_remove, - .id_table = inv_mpu_id, - .driver = { - .name = "inv-mpu6050", - .pm = INV_MPU6050_PMOPS, - .acpi_match_table = ACPI_PTR(inv_acpi_match), - }, -}; - -module_i2c_driver(inv_mpu_driver); +SIMPLE_DEV_PM_OPS(inv_mpu_pmops, inv_mpu_suspend, inv_mpu_resume); +EXPORT_SYMBOL_GPL(inv_mpu_pmops); MODULE_AUTHOR("Invensense Corporation"); MODULE_DESCRIPTION("Invensense device MPU6050 driver"); diff --git b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c new file mode 100644 index 0000000..e1fd7fa --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c @@ -0,0 +1,200 @@ +/* +* Copyright (C) 2012 Invensense, Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* 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 +#include +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu6050_select_bypass(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct iio_dev *indio_dev = i2c_mux_priv(muxc); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + /* Use the same mutex which was used everywhere to protect power-op */ + mutex_lock(&indio_dev->mlock); + if (!st->powerup_count) { + ret = regmap_write(st->map, st->reg->pwr_mgmt_1, 0); + if (ret) + goto write_error; + + usleep_range(INV_MPU6050_REG_UP_TIME_MIN, + INV_MPU6050_REG_UP_TIME_MAX); + } + if (!ret) { + st->powerup_count++; + ret = regmap_write(st->map, st->reg->int_pin_cfg, + INV_MPU6050_INT_PIN_CFG | + INV_MPU6050_BIT_BYPASS_EN); + } +write_error: + mutex_unlock(&indio_dev->mlock); + + return ret; +} + +static int inv_mpu6050_deselect_bypass(struct i2c_mux_core *muxc, u32 chan_id) +{ + struct iio_dev *indio_dev = i2c_mux_priv(muxc); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + /* It doesn't really mattter, if any of the calls fails */ + regmap_write(st->map, st->reg->int_pin_cfg, INV_MPU6050_INT_PIN_CFG); + st->powerup_count--; + if (!st->powerup_count) + regmap_write(st->map, st->reg->pwr_mgmt_1, + INV_MPU6050_BIT_SLEEP); + mutex_unlock(&indio_dev->mlock); + + return 0; +} + +static const char *inv_mpu_match_acpi_device(struct device *dev, int *chip_id) +{ + const struct acpi_device_id *id; + + id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!id) + return NULL; + + *chip_id = (int)id->driver_data; + + return dev_name(dev); +} + +/** + * inv_mpu_probe() - probe function. + * @client: i2c client. + * @id: i2c device id. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu6050_state *st; + int result, chip_type; + struct regmap *regmap; + const char *name; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EOPNOTSUPP; + + if (id) { + chip_type = (int)id->driver_data; + name = id->name; + } else if (ACPI_HANDLE(&client->dev)) { + name = inv_mpu_match_acpi_device(&client->dev, &chip_type); + if (!name) + return -ENODEV; + } else { + return -ENOSYS; + } + + regmap = devm_regmap_init_i2c(client, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to register i2c regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + result = inv_mpu_core_probe(regmap, client->irq, name, + NULL, chip_type); + if (result < 0) + return result; + + st = iio_priv(dev_get_drvdata(&client->dev)); + st->muxc = i2c_mux_alloc(client->adapter, &client->dev, + 1, 0, I2C_MUX_LOCKED, + inv_mpu6050_select_bypass, + inv_mpu6050_deselect_bypass); + if (!st->muxc) { + result = -ENOMEM; + goto out_unreg_device; + } + st->muxc->priv = dev_get_drvdata(&client->dev); + result = i2c_mux_add_adapter(st->muxc, 0, 0, 0); + if (result) + goto out_unreg_device; + + result = inv_mpu_acpi_create_mux_client(client); + if (result) + goto out_del_mux; + + return 0; + +out_del_mux: + i2c_mux_del_adapters(st->muxc); +out_unreg_device: + inv_mpu_core_remove(&client->dev); + return result; +} + +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu6050_state *st = iio_priv(indio_dev); + + inv_mpu_acpi_delete_mux_client(client); + i2c_mux_del_adapters(st->muxc); + + return inv_mpu_core_remove(&client->dev); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"mpu6050", INV_MPU6050}, + {"mpu6500", INV_MPU6500}, + {"mpu9150", INV_MPU9150}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6500", INV_MPU6500}, + { }, +}; + +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct i2c_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .acpi_match_table = ACPI_PTR(inv_acpi_match), + .name = "inv-mpu6050-i2c", + .pm = &inv_mpu_pmops, + }, +}; + +module_i2c_driver(inv_mpu_driver); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device MPU6050 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h index db0a4a2..3bf8544 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_iio.h @@ -11,10 +11,12 @@ * GNU General Public License for more details. */ #include +#include #include #include #include #include +#include #include #include #include @@ -38,6 +40,9 @@ * @int_enable: Interrupt enable register. * @pwr_mgmt_1: Controls chip's power state and clock source. * @pwr_mgmt_2: Controls power state of individual sensors. + * @int_pin_cfg; Controls interrupt pin configuration. + * @accl_offset: Controls the accelerometer calibration offset. + * @gyro_offset: Controls the gyroscope calibration offset. */ struct inv_mpu6050_reg_map { u8 sample_rate_div; @@ -55,12 +60,16 @@ struct inv_mpu6050_reg_map { u8 pwr_mgmt_1; u8 pwr_mgmt_2; u8 int_pin_cfg; + u8 accl_offset; + u8 gyro_offset; }; /*device enum */ enum inv_devices { INV_MPU6050, INV_MPU6500, + INV_MPU6000, + INV_MPU9150, INV_NUM_PARTS }; @@ -86,13 +95,13 @@ struct inv_mpu6050_chip_config { /** * struct inv_mpu6050_hw - Other important hardware information. - * @num_reg: Number of registers on device. + * @whoami: Self identification byte from WHO_AM_I register * @name: name of the chip. * @reg: register map of the chip. * @config: configuration of the chip. */ struct inv_mpu6050_hw { - u8 num_reg; + u8 whoami; u8 *name; const struct inv_mpu6050_reg_map *reg; const struct inv_mpu6050_chip_config *config; @@ -107,9 +116,11 @@ struct inv_mpu6050_hw { * @hw: Other hardware-specific information. * @chip_type: chip type. * @time_stamp_lock: spin lock to time stamp. - * @client: i2c client handle. - * @plat_data: platform data. + * @plat_data: platform data (deprecated in favor of @orientation). + * @orientation: sensor chip orientation relative to main hardware. * @timestamps: kfifo queue to store time stamp. + * @map regmap pointer. + * @irq interrupt number. */ struct inv_mpu6050_state { #define TIMESTAMP_FIFO_SIZE 16 @@ -119,15 +130,20 @@ struct inv_mpu6050_state { const struct inv_mpu6050_hw *hw; enum inv_devices chip_type; spinlock_t time_stamp_lock; - struct i2c_client *client; - struct i2c_adapter *mux_adapter; + struct i2c_mux_core *muxc; struct i2c_client *mux_client; unsigned int powerup_count; struct inv_mpu6050_platform_data plat_data; + struct iio_mount_matrix orientation; DECLARE_KFIFO(timestamps, long long, TIMESTAMP_FIFO_SIZE); + struct regmap *map; + int irq; }; /*register and associated bit definition*/ +#define INV_MPU6050_REG_ACCEL_OFFSET 0x06 +#define INV_MPU6050_REG_GYRO_OFFSET 0x13 + #define INV_MPU6050_REG_SAMPLE_RATE_DIV 0x19 #define INV_MPU6050_REG_CONFIG 0x1A #define INV_MPU6050_REG_GYRO_CONFIG 0x1B @@ -151,6 +167,7 @@ struct inv_mpu6050_state { #define INV_MPU6050_BIT_I2C_MST_EN 0x20 #define INV_MPU6050_BIT_FIFO_EN 0x40 #define INV_MPU6050_BIT_DMP_EN 0x80 +#define INV_MPU6050_BIT_I2C_IF_DIS 0x10 #define INV_MPU6050_REG_PWR_MGMT_1 0x6B #define INV_MPU6050_BIT_H_RESET 0x80 @@ -167,10 +184,18 @@ struct inv_mpu6050_state { #define INV_MPU6050_BYTES_PER_3AXIS_SENSOR 6 #define INV_MPU6050_FIFO_COUNT_BYTE 2 #define INV_MPU6050_FIFO_THRESHOLD 500 + +/* mpu6500 registers */ +#define INV_MPU6500_REG_ACCEL_OFFSET 0x77 + +/* delay time in milliseconds */ #define INV_MPU6050_POWER_UP_TIME 100 #define INV_MPU6050_TEMP_UP_TIME 100 #define INV_MPU6050_SENSOR_UP_TIME 30 -#define INV_MPU6050_REG_UP_TIME 5 + +/* delay time in microseconds */ +#define INV_MPU6050_REG_UP_TIME_MIN 5000 +#define INV_MPU6050_REG_UP_TIME_MAX 10000 #define INV_MPU6050_TEMP_OFFSET 12421 #define INV_MPU6050_TEMP_SCALE 2941 @@ -185,6 +210,7 @@ struct inv_mpu6050_state { #define INV_MPU6050_REG_INT_PIN_CFG 0x37 #define INV_MPU6050_BIT_BYPASS_EN 0x2 +#define INV_MPU6050_INT_PIN_CFG 0 /* init parameters */ #define INV_MPU6050_INIT_FIFO_RATE 50 @@ -193,6 +219,13 @@ struct inv_mpu6050_state { #define INV_MPU6050_MIN_FIFO_RATE 4 #define INV_MPU6050_ONE_K_HZ 1000 +#define INV_MPU6050_REG_WHOAMI 117 + +#define INV_MPU6000_WHOAMI_VALUE 0x68 +#define INV_MPU6050_WHOAMI_VALUE 0x68 +#define INV_MPU6500_WHOAMI_VALUE 0x70 +#define INV_MPU9150_WHOAMI_VALUE 0x68 + /* scan element definition */ enum inv_mpu6050_scan { INV_MPU6050_SCAN_ACCL_X, @@ -252,5 +285,10 @@ int inv_reset_fifo(struct iio_dev *indio_dev); int inv_mpu6050_switch_engine(struct inv_mpu6050_state *st, bool en, u32 mask); int inv_mpu6050_write_reg(struct inv_mpu6050_state *st, int reg, u8 val); int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on); -int inv_mpu_acpi_create_mux_client(struct inv_mpu6050_state *st); -void inv_mpu_acpi_delete_mux_client(struct inv_mpu6050_state *st); +int inv_mpu_acpi_create_mux_client(struct i2c_client *client); +void inv_mpu_acpi_delete_mux_client(struct i2c_client *client); +int inv_mpu_core_probe(struct regmap *regmap, int irq, const char *name, + int (*inv_mpu_bus_setup)(struct iio_dev *), int chip_type); +int inv_mpu_core_remove(struct device *dev); +int inv_mpu6050_set_power_itg(struct inv_mpu6050_state *st, bool power_on); +extern const struct dev_pm_ops inv_mpu_pmops; diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c index ba27e27..d070062 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_ring.c @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -41,23 +40,24 @@ int inv_reset_fifo(struct iio_dev *indio_dev) struct inv_mpu6050_state *st = iio_priv(indio_dev); /* disable interrupt */ - result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + result = regmap_write(st->map, st->reg->int_enable, 0); if (result) { - dev_err(&st->client->dev, "int_enable failed %d\n", result); + dev_err(regmap_get_device(st->map), "int_enable failed %d\n", + result); return result; } /* disable the sensor output to FIFO */ - result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + result = regmap_write(st->map, st->reg->fifo_en, 0); if (result) goto reset_fifo_fail; /* disable fifo reading */ - result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + result = regmap_write(st->map, st->reg->user_ctrl, 0); if (result) goto reset_fifo_fail; /* reset FIFO*/ - result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, - INV_MPU6050_BIT_FIFO_RST); + result = regmap_write(st->map, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_RST); if (result) goto reset_fifo_fail; @@ -67,14 +67,14 @@ int inv_reset_fifo(struct iio_dev *indio_dev) /* enable interrupt */ if (st->chip_config.accl_fifo_enable || st->chip_config.gyro_fifo_enable) { - result = inv_mpu6050_write_reg(st, st->reg->int_enable, - INV_MPU6050_BIT_DATA_RDY_EN); + result = regmap_write(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); if (result) return result; } /* enable FIFO reading and I2C master interface*/ - result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, - INV_MPU6050_BIT_FIFO_EN); + result = regmap_write(st->map, st->reg->user_ctrl, + INV_MPU6050_BIT_FIFO_EN); if (result) goto reset_fifo_fail; /* enable sensor output to FIFO */ @@ -83,16 +83,16 @@ int inv_reset_fifo(struct iio_dev *indio_dev) d |= INV_MPU6050_BITS_GYRO_OUT; if (st->chip_config.accl_fifo_enable) d |= INV_MPU6050_BIT_ACCEL_OUT; - result = inv_mpu6050_write_reg(st, st->reg->fifo_en, d); + result = regmap_write(st->map, st->reg->fifo_en, d); if (result) goto reset_fifo_fail; return 0; reset_fifo_fail: - dev_err(&st->client->dev, "reset fifo failed %d\n", result); - result = inv_mpu6050_write_reg(st, st->reg->int_enable, - INV_MPU6050_BIT_DATA_RDY_EN); + dev_err(regmap_get_device(st->map), "reset fifo failed %d\n", result); + result = regmap_write(st->map, st->reg->int_enable, + INV_MPU6050_BIT_DATA_RDY_EN); return result; } @@ -109,7 +109,7 @@ irqreturn_t inv_mpu6050_irq_handler(int irq, void *p) timestamp = iio_get_time_ns(); kfifo_in_spinlocked(&st->timestamps, ×tamp, 1, - &st->time_stamp_lock); + &st->time_stamp_lock); return IRQ_WAKE_THREAD; } @@ -143,10 +143,9 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) * read fifo_count register to know how many bytes inside FIFO * right now */ - result = i2c_smbus_read_i2c_block_data(st->client, - st->reg->fifo_count_h, - INV_MPU6050_FIFO_COUNT_BYTE, data); - if (result != INV_MPU6050_FIFO_COUNT_BYTE) + result = regmap_bulk_read(st->map, st->reg->fifo_count_h, data, + INV_MPU6050_FIFO_COUNT_BYTE); + if (result) goto end_session; fifo_count = be16_to_cpup((__be16 *)(&data[0])); if (fifo_count < bytes_per_datum) @@ -158,22 +157,21 @@ irqreturn_t inv_mpu6050_read_fifo(int irq, void *p) goto flush_fifo; /* Timestamp mismatch. */ if (kfifo_len(&st->timestamps) > - fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) - goto flush_fifo; + fifo_count / bytes_per_datum + INV_MPU6050_TIME_STAMP_TOR) + goto flush_fifo; while (fifo_count >= bytes_per_datum) { - result = i2c_smbus_read_i2c_block_data(st->client, - st->reg->fifo_r_w, - bytes_per_datum, data); - if (result != bytes_per_datum) + result = regmap_bulk_read(st->map, st->reg->fifo_r_w, + data, bytes_per_datum); + if (result) goto flush_fifo; result = kfifo_out(&st->timestamps, ×tamp, 1); /* when there is no timestamp, put timestamp as 0 */ - if (0 == result) + if (result == 0) timestamp = 0; result = iio_push_to_buffers_with_timestamp(indio_dev, data, - timestamp); + timestamp); if (result) goto flush_fifo; fifo_count -= bytes_per_datum; diff --git b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c new file mode 100644 index 0000000..190a4a5 --- /dev/null +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_spi.c @@ -0,0 +1,111 @@ +/* +* Copyright (C) 2015 Intel Corporation Inc. +* +* This software is licensed under the terms of the GNU General Public +* License version 2, as published by the Free Software Foundation, and +* may be copied, distributed, and modified under those terms. +* +* 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 +#include +#include +#include +#include +#include "inv_mpu_iio.h" + +static const struct regmap_config inv_mpu_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_mpu_i2c_disable(struct iio_dev *indio_dev) +{ + struct inv_mpu6050_state *st = iio_priv(indio_dev); + int ret = 0; + + ret = inv_mpu6050_set_power_itg(st, true); + if (ret) + return ret; + + ret = regmap_write(st->map, INV_MPU6050_REG_USER_CTRL, + INV_MPU6050_BIT_I2C_IF_DIS); + if (ret) { + inv_mpu6050_set_power_itg(st, false); + return ret; + } + + return inv_mpu6050_set_power_itg(st, false); +} + +static int inv_mpu_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *spi_id; + const struct acpi_device_id *acpi_id; + const char *name = NULL; + enum inv_devices chip_type; + + if ((spi_id = spi_get_device_id(spi))) { + chip_type = (enum inv_devices)spi_id->driver_data; + name = spi_id->name; + } else if ((acpi_id = acpi_match_device(spi->dev.driver->acpi_match_table, &spi->dev))) { + chip_type = (enum inv_devices)acpi_id->driver_data; + } else { + return -ENODEV; + } + + regmap = devm_regmap_init_spi(spi, &inv_mpu_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + return inv_mpu_core_probe(regmap, spi->irq, name, + inv_mpu_i2c_disable, chip_type); +} + +static int inv_mpu_remove(struct spi_device *spi) +{ + return inv_mpu_core_remove(&spi->dev); +} + +/* + * device id table is used to identify what device can be + * supported by this driver + */ +static const struct spi_device_id inv_mpu_id[] = { + {"mpu6000", INV_MPU6000}, + {"mpu6500", INV_MPU6500}, + {"mpu9150", INV_MPU9150}, + {} +}; + +MODULE_DEVICE_TABLE(spi, inv_mpu_id); + +static const struct acpi_device_id inv_acpi_match[] = { + {"INVN6000", INV_MPU6000}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, inv_acpi_match); + +static struct spi_driver inv_mpu_driver = { + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .id_table = inv_mpu_id, + .driver = { + .acpi_match_table = ACPI_PTR(inv_acpi_match), + .name = "inv-mpu6000-spi", + .pm = &inv_mpu_pmops, + }, +}; + +module_spi_driver(inv_mpu_driver); + +MODULE_AUTHOR("Adriana Reus "); +MODULE_DESCRIPTION("Invensense device MPU6000 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c index 844610c..e8818d4 100644 --- a/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c +++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_trigger.c @@ -19,19 +19,19 @@ static void inv_scan_query(struct iio_dev *indio_dev) st->chip_config.gyro_fifo_enable = test_bit(INV_MPU6050_SCAN_GYRO_X, - indio_dev->active_scan_mask) || - test_bit(INV_MPU6050_SCAN_GYRO_Y, - indio_dev->active_scan_mask) || - test_bit(INV_MPU6050_SCAN_GYRO_Z, - indio_dev->active_scan_mask); + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_GYRO_Z, + indio_dev->active_scan_mask); st->chip_config.accl_fifo_enable = test_bit(INV_MPU6050_SCAN_ACCL_X, - indio_dev->active_scan_mask) || - test_bit(INV_MPU6050_SCAN_ACCL_Y, - indio_dev->active_scan_mask) || - test_bit(INV_MPU6050_SCAN_ACCL_Z, - indio_dev->active_scan_mask); + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Y, + indio_dev->active_scan_mask) || + test_bit(INV_MPU6050_SCAN_ACCL_Z, + indio_dev->active_scan_mask); } /** @@ -65,15 +65,15 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) if (result) return result; } else { - result = inv_mpu6050_write_reg(st, st->reg->fifo_en, 0); + result = regmap_write(st->map, st->reg->fifo_en, 0); if (result) return result; - result = inv_mpu6050_write_reg(st, st->reg->int_enable, 0); + result = regmap_write(st->map, st->reg->int_enable, 0); if (result) return result; - result = inv_mpu6050_write_reg(st, st->reg->user_ctrl, 0); + result = regmap_write(st->map, st->reg->user_ctrl, 0); if (result) return result; @@ -101,7 +101,7 @@ static int inv_mpu6050_set_enable(struct iio_dev *indio_dev, bool enable) * @state: Desired trigger state */ static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, - bool state) + bool state) { return inv_mpu6050_set_enable(iio_trigger_get_drvdata(trig), state); } @@ -123,7 +123,7 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev) if (!st->trig) return -ENOMEM; - ret = devm_request_irq(&indio_dev->dev, st->client->irq, + ret = devm_request_irq(&indio_dev->dev, st->irq, &iio_trigger_generic_data_rdy_poll, IRQF_TRIGGER_RISING, "inv_mpu", @@ -131,7 +131,7 @@ int inv_mpu6050_probe_trigger(struct iio_dev *indio_dev) if (ret) return ret; - st->trig->dev.parent = &st->client->dev; + st->trig->dev.parent = regmap_get_device(st->map); st->trig->ops = &inv_mpu_trigger_ops; iio_trigger_set_drvdata(st->trig, indio_dev); diff --git a/drivers/iio/imu/kmx61.c b/drivers/iio/imu/kmx61.c index dbf5e99..2e7dd57 100644 --- a/drivers/iio/imu/kmx61.c +++ b/drivers/iio/imu/kmx61.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -1390,6 +1389,14 @@ static int kmx61_probe(struct i2c_client *client, } } + ret = pm_runtime_set_active(&client->dev); + if (ret < 0) + goto err_buffer_cleanup_mag; + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); + pm_runtime_use_autosuspend(&client->dev); + ret = iio_device_register(data->acc_indio_dev); if (ret < 0) { dev_err(&client->dev, "Failed to register acc iio device\n"); @@ -1402,18 +1409,8 @@ static int kmx61_probe(struct i2c_client *client, goto err_iio_unregister_acc; } - ret = pm_runtime_set_active(&client->dev); - if (ret < 0) - goto err_iio_unregister_mag; - - pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, KMX61_SLEEP_DELAY_MS); - pm_runtime_use_autosuspend(&client->dev); - return 0; -err_iio_unregister_mag: - iio_device_unregister(data->mag_indio_dev); err_iio_unregister_acc: iio_device_unregister(data->acc_indio_dev); err_buffer_cleanup_mag: @@ -1437,13 +1434,13 @@ static int kmx61_remove(struct i2c_client *client) { struct kmx61_data *data = i2c_get_clientdata(client); + iio_device_unregister(data->acc_indio_dev); + iio_device_unregister(data->mag_indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(data->acc_indio_dev); - iio_device_unregister(data->mag_indio_dev); - if (client->irq > 0) { iio_triggered_buffer_cleanup(data->acc_indio_dev); iio_triggered_buffer_cleanup(data->mag_indio_dev); diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 7afd226..49bf9c5 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -204,7 +204,8 @@ void iio_buffer_init(struct iio_buffer *buffer) INIT_LIST_HEAD(&buffer->buffer_list); init_waitqueue_head(&buffer->pollq); kref_init(&buffer->ref); - buffer->watermark = 1; + if (!buffer->watermark) + buffer->watermark = 1; } EXPORT_SYMBOL(iio_buffer_init); @@ -522,33 +523,41 @@ static ssize_t iio_buffer_show_enable(struct device *dev, return sprintf(buf, "%d\n", iio_buffer_is_active(indio_dev->buffer)); } +static unsigned int iio_storage_bytes_for_si(struct iio_dev *indio_dev, + unsigned int scan_index) +{ + const struct iio_chan_spec *ch; + unsigned int bytes; + + ch = iio_find_channel_from_si(indio_dev, scan_index); + bytes = ch->scan_type.storagebits / 8; + if (ch->scan_type.repeat > 1) + bytes *= ch->scan_type.repeat; + return bytes; +} + +static unsigned int iio_storage_bytes_for_timestamp(struct iio_dev *indio_dev) +{ + return iio_storage_bytes_for_si(indio_dev, + indio_dev->scan_index_timestamp); +} + static int iio_compute_scan_bytes(struct iio_dev *indio_dev, const unsigned long *mask, bool timestamp) { - const struct iio_chan_spec *ch; unsigned bytes = 0; int length, i; /* How much space will the demuxed element take? */ for_each_set_bit(i, mask, indio_dev->masklength) { - ch = iio_find_channel_from_si(indio_dev, i); - if (ch->scan_type.repeat > 1) - length = ch->scan_type.storagebits / 8 * - ch->scan_type.repeat; - else - length = ch->scan_type.storagebits / 8; + length = iio_storage_bytes_for_si(indio_dev, i); bytes = ALIGN(bytes, length); bytes += length; } + if (timestamp) { - ch = iio_find_channel_from_si(indio_dev, - indio_dev->scan_index_timestamp); - if (ch->scan_type.repeat > 1) - length = ch->scan_type.storagebits / 8 * - ch->scan_type.repeat; - else - length = ch->scan_type.storagebits / 8; + length = iio_storage_bytes_for_timestamp(indio_dev); bytes = ALIGN(bytes, length); bytes += length; } @@ -578,6 +587,22 @@ static void iio_buffer_deactivate_all(struct iio_dev *indio_dev) iio_buffer_deactivate(buffer); } +static int iio_buffer_enable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->enable) + return 0; + return buffer->access->enable(buffer, indio_dev); +} + +static int iio_buffer_disable(struct iio_buffer *buffer, + struct iio_dev *indio_dev) +{ + if (!buffer->access->disable) + return 0; + return buffer->access->disable(buffer, indio_dev); +} + static void iio_buffer_update_bytes_per_datum(struct iio_dev *indio_dev, struct iio_buffer *buffer) { @@ -621,6 +646,7 @@ static void iio_free_scan_mask(struct iio_dev *indio_dev, struct iio_device_config { unsigned int mode; + unsigned int watermark; const unsigned long *scan_mask; unsigned int scan_bytes; bool scan_timestamp; @@ -638,6 +664,7 @@ static int iio_verify_update(struct iio_dev *indio_dev, unsigned int modes; memset(config, 0, sizeof(*config)); + config->watermark = ~0; /* * If there is just one buffer and we are removing it there is nothing @@ -653,10 +680,14 @@ static int iio_verify_update(struct iio_dev *indio_dev, if (buffer == remove_buffer) continue; modes &= buffer->access->modes; + config->watermark = min(config->watermark, buffer->watermark); } - if (insert_buffer) + if (insert_buffer) { modes &= insert_buffer->access->modes; + config->watermark = min(config->watermark, + insert_buffer->watermark); + } /* Definitely possible for devices to support both of these. */ if ((modes & INDIO_BUFFER_TRIGGERED) && indio_dev->trig) { @@ -724,6 +755,7 @@ static int iio_verify_update(struct iio_dev *indio_dev, static int iio_enable_buffers(struct iio_dev *indio_dev, struct iio_device_config *config) { + struct iio_buffer *buffer; int ret; indio_dev->active_scan_mask = config->scan_mask; @@ -754,6 +786,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev, } } + if (indio_dev->info->hwfifo_set_watermark) + indio_dev->info->hwfifo_set_watermark(indio_dev, + config->watermark); + + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret = iio_buffer_enable(buffer, indio_dev); + if (ret) + goto err_disable_buffers; + } + indio_dev->currentmode = config->mode; if (indio_dev->setup_ops->postenable) { @@ -761,12 +803,16 @@ static int iio_enable_buffers(struct iio_dev *indio_dev, if (ret) { dev_dbg(&indio_dev->dev, "Buffer not started: postenable failed (%d)\n", ret); - goto err_run_postdisable; + goto err_disable_buffers; } } return 0; +err_disable_buffers: + list_for_each_entry_continue_reverse(buffer, &indio_dev->buffer_list, + buffer_list) + iio_buffer_disable(buffer, indio_dev); err_run_postdisable: indio_dev->currentmode = INDIO_DIRECT_MODE; if (indio_dev->setup_ops->postdisable) @@ -779,6 +825,7 @@ err_undo_config: static int iio_disable_buffers(struct iio_dev *indio_dev) { + struct iio_buffer *buffer; int ret = 0; int ret2; @@ -799,6 +846,12 @@ static int iio_disable_buffers(struct iio_dev *indio_dev) ret = ret2; } + list_for_each_entry(buffer, &indio_dev->buffer_list, buffer_list) { + ret2 = iio_buffer_disable(buffer, indio_dev); + if (ret2 && !ret) + ret = ret2; + } + indio_dev->currentmode = INDIO_DIRECT_MODE; if (indio_dev->setup_ops->postdisable) { @@ -985,9 +1038,6 @@ static ssize_t iio_buffer_store_watermark(struct device *dev, } buffer->watermark = val; - - if (indio_dev->info->hwfifo_set_watermark) - indio_dev->info->hwfifo_set_watermark(indio_dev, val); out: mutex_unlock(&indio_dev->mlock); @@ -1002,6 +1052,8 @@ static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, iio_buffer_show_enable, iio_buffer_store_enable); static DEVICE_ATTR(watermark, S_IRUGO | S_IWUSR, iio_buffer_show_watermark, iio_buffer_store_watermark); +static struct device_attribute dev_attr_watermark_ro = __ATTR(watermark, + S_IRUGO, iio_buffer_show_watermark, NULL); static struct attribute *iio_buffer_attrs[] = { &dev_attr_length.attr, @@ -1044,6 +1096,9 @@ int iio_buffer_alloc_sysfs_and_mask(struct iio_dev *indio_dev) if (!buffer->access->set_length) attr[0] = &dev_attr_length_ro.attr; + if (buffer->access->flags & INDIO_BUFFER_FLAG_FIXED_WATERMARK) + attr[2] = &dev_attr_watermark_ro.attr; + if (buffer->attrs) memcpy(&attr[ARRAY_SIZE(iio_buffer_attrs)], buffer->attrs, sizeof(struct attribute *) * attrcount); @@ -1253,7 +1308,6 @@ static int iio_buffer_add_demux(struct iio_buffer *buffer, static int iio_buffer_update_demux(struct iio_dev *indio_dev, struct iio_buffer *buffer) { - const struct iio_chan_spec *ch; int ret, in_ind = -1, out_ind, length; unsigned in_loc = 0, out_loc = 0; struct iio_demux_table *p = NULL; @@ -1280,21 +1334,11 @@ static int iio_buffer_update_demux(struct iio_dev *indio_dev, in_ind = find_next_bit(indio_dev->active_scan_mask, indio_dev->masklength, in_ind + 1); - ch = iio_find_channel_from_si(indio_dev, in_ind); - if (ch->scan_type.repeat > 1) - length = ch->scan_type.storagebits / 8 * - ch->scan_type.repeat; - else - length = ch->scan_type.storagebits / 8; + length = iio_storage_bytes_for_si(indio_dev, in_ind); /* Make sure we are aligned */ in_loc = roundup(in_loc, length) + length; } - ch = iio_find_channel_from_si(indio_dev, in_ind); - if (ch->scan_type.repeat > 1) - length = ch->scan_type.storagebits / 8 * - ch->scan_type.repeat; - else - length = ch->scan_type.storagebits / 8; + length = iio_storage_bytes_for_si(indio_dev, in_ind); out_loc = roundup(out_loc, length); in_loc = roundup(in_loc, length); ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); @@ -1305,13 +1349,7 @@ static int iio_buffer_update_demux(struct iio_dev *indio_dev, } /* Relies on scan_timestamp being last */ if (buffer->scan_timestamp) { - ch = iio_find_channel_from_si(indio_dev, - indio_dev->scan_index_timestamp); - if (ch->scan_type.repeat > 1) - length = ch->scan_type.storagebits / 8 * - ch->scan_type.repeat; - else - length = ch->scan_type.storagebits / 8; + length = iio_storage_bytes_for_timestamp(indio_dev); out_loc = roundup(out_loc, length); in_loc = roundup(in_loc, length); ret = iio_buffer_add_demux(buffer, &p, in_loc, out_loc, length); diff --git b/drivers/iio/industrialio-configfs.c b/drivers/iio/industrialio-configfs.c new file mode 100644 index 0000000..45ce2bc --- /dev/null +++ b/drivers/iio/industrialio-configfs.c @@ -0,0 +1,51 @@ +/* + * Industrial I/O configfs bits + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include + +static struct config_item_type iio_root_group_type = { + .ct_owner = THIS_MODULE, +}; + +struct configfs_subsystem iio_configfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "iio", + .ci_type = &iio_root_group_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(iio_configfs_subsys.su_mutex), +}; +EXPORT_SYMBOL(iio_configfs_subsys); + +static int __init iio_configfs_init(void) +{ + config_group_init(&iio_configfs_subsys.su_group); + + return configfs_register_subsystem(&iio_configfs_subsys); +} +module_init(iio_configfs_init); + +static void __exit iio_configfs_exit(void) +{ + configfs_unregister_subsystem(&iio_configfs_subsys); +} +module_exit(iio_configfs_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O configfs support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 159ede6..e6319a9 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "iio_core.h" #include "iio_core_trigger.h" @@ -77,6 +78,8 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_VELOCITY] = "velocity", [IIO_CONCENTRATION] = "concentration", [IIO_RESISTANCE] = "resistance", + [IIO_PH] = "ph", + [IIO_UVINDEX] = "uvindex", }; static const char * const iio_modifier_names[] = { @@ -99,6 +102,7 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_LIGHT_RED] = "red", [IIO_MOD_LIGHT_GREEN] = "green", [IIO_MOD_LIGHT_BLUE] = "blue", + [IIO_MOD_LIGHT_UV] = "uv", [IIO_MOD_QUATERNION] = "quaternion", [IIO_MOD_TEMP_AMBIENT] = "ambient", [IIO_MOD_TEMP_OBJECT] = "object", @@ -408,6 +412,88 @@ ssize_t iio_enum_write(struct iio_dev *indio_dev, } EXPORT_SYMBOL_GPL(iio_enum_write); +static const struct iio_mount_matrix iio_mount_idmatrix = { + .rotation = { + "1", "0", "0", + "0", "1", "0", + "0", "0", "1" + } +}; + +static int iio_setup_mount_idmatrix(const struct device *dev, + struct iio_mount_matrix *matrix) +{ + *matrix = iio_mount_idmatrix; + dev_info(dev, "mounting matrix not found: using identity...\n"); + return 0; +} + +ssize_t iio_show_mount_matrix(struct iio_dev *indio_dev, uintptr_t priv, + const struct iio_chan_spec *chan, char *buf) +{ + const struct iio_mount_matrix *mtx = ((iio_get_mount_matrix_t *) + priv)(indio_dev, chan); + + if (IS_ERR(mtx)) + return PTR_ERR(mtx); + + if (!mtx) + mtx = &iio_mount_idmatrix; + + return snprintf(buf, PAGE_SIZE, "%s, %s, %s; %s, %s, %s; %s, %s, %s\n", + mtx->rotation[0], mtx->rotation[1], mtx->rotation[2], + mtx->rotation[3], mtx->rotation[4], mtx->rotation[5], + mtx->rotation[6], mtx->rotation[7], mtx->rotation[8]); +} +EXPORT_SYMBOL_GPL(iio_show_mount_matrix); + +/** + * of_iio_read_mount_matrix() - retrieve iio device mounting matrix from + * device-tree "mount-matrix" property + * @dev: device the mounting matrix property is assigned to + * @propname: device specific mounting matrix property name + * @matrix: where to store retrieved matrix + * + * If device is assigned no mounting matrix property, a default 3x3 identity + * matrix will be filled in. + * + * Return: 0 if success, or a negative error code on failure. + */ +#ifdef CONFIG_OF +int of_iio_read_mount_matrix(const struct device *dev, + const char *propname, + struct iio_mount_matrix *matrix) +{ + if (dev->of_node) { + int err = of_property_read_string_array(dev->of_node, + propname, matrix->rotation, + ARRAY_SIZE(iio_mount_idmatrix.rotation)); + + if (err == ARRAY_SIZE(iio_mount_idmatrix.rotation)) + return 0; + + if (err >= 0) + /* Invalid number of matrix entries. */ + return -EINVAL; + + if (err != -EINVAL) + /* Invalid matrix declaration format. */ + return err; + } + + /* Matrix was not declared at all: fallback to identity. */ + return iio_setup_mount_idmatrix(dev, matrix); +} +#else +int of_iio_read_mount_matrix(const struct device *dev, + const char *propname, + struct iio_mount_matrix *matrix) +{ + return iio_setup_mount_idmatrix(dev, matrix); +} +#endif +EXPORT_SYMBOL(of_iio_read_mount_matrix); + /** * iio_format_value() - Formats a IIO value into its string representation * @buf: The buffer to which the formatted value gets written @@ -433,16 +519,15 @@ ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) scale_db = true; case IIO_VAL_INT_PLUS_MICRO: if (vals[1] < 0) - return sprintf(buf, "-%ld.%06u%s\n", abs(vals[0]), - -vals[1], - scale_db ? " dB" : ""); + return sprintf(buf, "-%d.%06u%s\n", abs(vals[0]), + -vals[1], scale_db ? " dB" : ""); else return sprintf(buf, "%d.%06u%s\n", vals[0], vals[1], scale_db ? " dB" : ""); case IIO_VAL_INT_PLUS_NANO: if (vals[1] < 0) - return sprintf(buf, "-%ld.%09u\n", abs(vals[0]), - -vals[1]); + return sprintf(buf, "-%d.%09u\n", abs(vals[0]), + -vals[1]); else return sprintf(buf, "%d.%09u\n", vals[0], vals[1]); case IIO_VAL_FRACTIONAL: @@ -470,6 +555,7 @@ ssize_t iio_format_value(char *buf, unsigned int type, int size, int *vals) return 0; } } +EXPORT_SYMBOL_GPL(iio_format_value); static ssize_t iio_read_channel_info(struct device *dev, struct device_attribute *attr, @@ -512,6 +598,12 @@ int iio_str_to_fixpoint(const char *str, int fract_mult, int i = 0, f = 0; bool integer_part = true, negative = false; + if (fract_mult == 0) { + *fract = 0; + + return kstrtoint(str, 0, integer); + } + if (str[0] == '-') { negative = true; str++; @@ -571,6 +663,9 @@ static ssize_t iio_write_channel_info(struct device *dev, if (indio_dev->info->write_raw_get_fmt) switch (indio_dev->info->write_raw_get_fmt(indio_dev, this_attr->c, this_attr->address)) { + case IIO_VAL_INT: + fract_mult = 0; + break; case IIO_VAL_INT_PLUS_MICRO: fract_mult = 100000; break; @@ -1365,6 +1460,44 @@ void devm_iio_device_unregister(struct device *dev, struct iio_dev *indio_dev) } EXPORT_SYMBOL_GPL(devm_iio_device_unregister); +/** + * iio_device_claim_direct_mode - Keep device in direct mode + * @indio_dev: the iio_dev associated with the device + * + * If the device is in direct mode it is guaranteed to stay + * that way until iio_device_release_direct_mode() is called. + * + * Use with iio_device_release_direct_mode() + * + * Returns: 0 on success, -EBUSY on failure + */ +int iio_device_claim_direct_mode(struct iio_dev *indio_dev) +{ + mutex_lock(&indio_dev->mlock); + + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + return 0; +} +EXPORT_SYMBOL_GPL(iio_device_claim_direct_mode); + +/** + * iio_device_release_direct_mode - releases claim on direct mode + * @indio_dev: the iio_dev associated with the device + * + * Release the claim. Device is no longer guaranteed to stay + * in direct mode. + * + * Use with iio_device_claim_direct_mode() + */ +void iio_device_release_direct_mode(struct iio_dev *indio_dev) +{ + mutex_unlock(&indio_dev->mlock); +} +EXPORT_SYMBOL_GPL(iio_device_release_direct_mode); + subsys_initcall(iio_init); module_exit(iio_exit); diff --git b/drivers/iio/industrialio-sw-trigger.c b/drivers/iio/industrialio-sw-trigger.c new file mode 100644 index 0000000..8d24fb1 --- /dev/null +++ b/drivers/iio/industrialio-sw-trigger.c @@ -0,0 +1,182 @@ +/* + * The Industrial I/O core, software trigger functions + * + * Copyright (c) 2015 Intel Corporation + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct config_group *iio_triggers_group; +static struct config_item_type iio_trigger_type_group_type; + +static struct config_item_type iio_triggers_group_type = { + .ct_owner = THIS_MODULE, +}; + +static LIST_HEAD(iio_trigger_types_list); +static DEFINE_MUTEX(iio_trigger_types_lock); + +static +struct iio_sw_trigger_type *__iio_find_sw_trigger_type(const char *name, + unsigned len) +{ + struct iio_sw_trigger_type *t = NULL, *iter; + + list_for_each_entry(iter, &iio_trigger_types_list, list) + if (!strcmp(iter->name, name)) { + t = iter; + break; + } + + return t; +} + +int iio_register_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + int ret = 0; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + ret = -EBUSY; + else + list_add_tail(&t->list, &iio_trigger_types_list); + mutex_unlock(&iio_trigger_types_lock); + + if (ret) + return ret; + + t->group = configfs_register_default_group(iio_triggers_group, t->name, + &iio_trigger_type_group_type); + if (IS_ERR(t->group)) + ret = PTR_ERR(t->group); + + return ret; +} +EXPORT_SYMBOL(iio_register_sw_trigger_type); + +void iio_unregister_sw_trigger_type(struct iio_sw_trigger_type *t) +{ + struct iio_sw_trigger_type *iter; + + mutex_lock(&iio_trigger_types_lock); + iter = __iio_find_sw_trigger_type(t->name, strlen(t->name)); + if (iter) + list_del(&t->list); + mutex_unlock(&iio_trigger_types_lock); + + configfs_unregister_default_group(t->group); +} +EXPORT_SYMBOL(iio_unregister_sw_trigger_type); + +static +struct iio_sw_trigger_type *iio_get_sw_trigger_type(const char *name) +{ + struct iio_sw_trigger_type *t; + + mutex_lock(&iio_trigger_types_lock); + t = __iio_find_sw_trigger_type(name, strlen(name)); + if (t && !try_module_get(t->owner)) + t = NULL; + mutex_unlock(&iio_trigger_types_lock); + + return t; +} + +struct iio_sw_trigger *iio_sw_trigger_create(const char *type, const char *name) +{ + struct iio_sw_trigger *t; + struct iio_sw_trigger_type *tt; + + tt = iio_get_sw_trigger_type(type); + if (!tt) { + pr_err("Invalid trigger type: %s\n", type); + return ERR_PTR(-EINVAL); + } + t = tt->ops->probe(name); + if (IS_ERR(t)) + goto out_module_put; + + t->trigger_type = tt; + + return t; +out_module_put: + module_put(tt->owner); + return t; +} +EXPORT_SYMBOL(iio_sw_trigger_create); + +void iio_sw_trigger_destroy(struct iio_sw_trigger *t) +{ + struct iio_sw_trigger_type *tt = t->trigger_type; + + tt->ops->remove(t); + module_put(tt->owner); +} +EXPORT_SYMBOL(iio_sw_trigger_destroy); + +static struct config_group *trigger_make_group(struct config_group *group, + const char *name) +{ + struct iio_sw_trigger *t; + + t = iio_sw_trigger_create(group->cg_item.ci_name, name); + if (IS_ERR(t)) + return ERR_CAST(t); + + config_item_set_name(&t->group.cg_item, "%s", name); + + return &t->group; +} + +static void trigger_drop_group(struct config_group *group, + struct config_item *item) +{ + struct iio_sw_trigger *t = to_iio_sw_trigger(item); + + iio_sw_trigger_destroy(t); + config_item_put(item); +} + +static struct configfs_group_operations trigger_ops = { + .make_group = &trigger_make_group, + .drop_item = &trigger_drop_group, +}; + +static struct config_item_type iio_trigger_type_group_type = { + .ct_group_ops = &trigger_ops, + .ct_owner = THIS_MODULE, +}; + +static int __init iio_sw_trigger_init(void) +{ + iio_triggers_group = + configfs_register_default_group(&iio_configfs_subsys.su_group, + "triggers", + &iio_triggers_group_type); + return PTR_ERR_OR_ZERO(iio_triggers_group); +} +module_init(iio_sw_trigger_init); + +static void __exit iio_sw_trigger_exit(void) +{ + configfs_unregister_default_group(iio_triggers_group); +} +module_exit(iio_sw_trigger_exit); + +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Industrial I/O software triggers support"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c index 217e930..c4757e6 100644 --- a/drivers/iio/inkern.c +++ b/drivers/iio/inkern.c @@ -61,12 +61,10 @@ EXPORT_SYMBOL_GPL(iio_map_array_register); int iio_map_array_unregister(struct iio_dev *indio_dev) { int ret = -ENODEV; - struct iio_map_internal *mapi; - struct list_head *pos, *tmp; + struct iio_map_internal *mapi, *next; mutex_lock(&iio_map_list_lock); - list_for_each_safe(pos, tmp, &iio_map_list) { - mapi = list_entry(pos, struct iio_map_internal, l); + list_for_each_entry_safe(mapi, next, &iio_map_list, l) { if (indio_dev == mapi->indio_dev) { list_del(&mapi->l); kfree(mapi); @@ -358,6 +356,54 @@ void iio_channel_release(struct iio_channel *channel) } EXPORT_SYMBOL_GPL(iio_channel_release); +static void devm_iio_channel_free(struct device *dev, void *res) +{ + struct iio_channel *channel = *(struct iio_channel **)res; + + iio_channel_release(channel); +} + +static int devm_iio_channel_match(struct device *dev, void *res, void *data) +{ + struct iio_channel **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +struct iio_channel *devm_iio_channel_get(struct device *dev, + const char *channel_name) +{ + struct iio_channel **ptr, *channel; + + ptr = devres_alloc(devm_iio_channel_free, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + channel = iio_channel_get(dev, channel_name); + if (IS_ERR(channel)) { + devres_free(ptr); + return channel; + } + + *ptr = channel; + devres_add(dev, ptr); + + return channel; +} +EXPORT_SYMBOL_GPL(devm_iio_channel_get); + +void devm_iio_channel_release(struct device *dev, struct iio_channel *channel) +{ + WARN_ON(devres_release(dev, devm_iio_channel_free, + devm_iio_channel_match, channel)); +} +EXPORT_SYMBOL_GPL(devm_iio_channel_release); + struct iio_channel *iio_channel_get_all(struct device *dev) { const char *name; @@ -443,6 +489,42 @@ void iio_channel_release_all(struct iio_channel *channels) } EXPORT_SYMBOL_GPL(iio_channel_release_all); +static void devm_iio_channel_free_all(struct device *dev, void *res) +{ + struct iio_channel *channels = *(struct iio_channel **)res; + + iio_channel_release_all(channels); +} + +struct iio_channel *devm_iio_channel_get_all(struct device *dev) +{ + struct iio_channel **ptr, *channels; + + ptr = devres_alloc(devm_iio_channel_free_all, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + channels = iio_channel_get_all(dev); + if (IS_ERR(channels)) { + devres_free(ptr); + return channels; + } + + *ptr = channels; + devres_add(dev, ptr); + + return channels; +} +EXPORT_SYMBOL_GPL(devm_iio_channel_get_all); + +void devm_iio_channel_release_all(struct device *dev, + struct iio_channel *channels) +{ + WARN_ON(devres_release(dev, devm_iio_channel_free_all, + devm_iio_channel_match, channels)); +} +EXPORT_SYMBOL_GPL(devm_iio_channel_release_all); + static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, enum iio_chan_info_enum info) { @@ -454,7 +536,7 @@ static int iio_channel_read(struct iio_channel *chan, int *val, int *val2, if (val2 == NULL) val2 = &unused; - if(!iio_channel_has_info(chan->channel, info)) + if (!iio_channel_has_info(chan->channel, info)) return -EINVAL; if (chan->indio_dev->info->read_raw_multi) { diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index cfd3df8..7c566f5 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -73,6 +73,17 @@ config BH1750 To compile this driver as a module, choose M here: the module will be called bh1750. +config BH1780 + tristate "ROHM BH1780 ambient light sensor" + depends on I2C + depends on !SENSORS_BH1780 + help + Say Y here to build support for the ROHM BH1780GLI ambient + light sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1780. + config CM32181 depends on I2C tristate "CM32181 driver" @@ -223,6 +234,17 @@ config LTR501 This driver can also be built as a module. If so, the module will be called ltr501. +config MAX44000 + tristate "MAX44000 Ambient and Infrared Proximity Sensor" + depends on I2C + select REGMAP_I2C + help + Say Y here if you want to build support for Maxim Integrated's + MAX44000 ambient and infrared proximity sensor device. + + To compile this driver as a module, choose M here: + the module will be called max44000. + config OPT3001 tristate "Texas Instruments OPT3001 Light Sensor" depends on I2C @@ -320,4 +342,14 @@ config VCNL4000 To compile this driver as a module, choose M here: the module will be called vcnl4000. +config VEML6070 + tristate "VEML6070 UV A light sensor" + depends on I2C + help + Say Y here if you want to build a driver for the Vishay VEML6070 UV A + light sensor. + + To compile this driver as a module, choose M here: the + module will be called veml6070. + endmenu diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index b2c3105..6f2a3c6 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_AL3320A) += al3320a.o obj-$(CONFIG_APDS9300) += apds9300.o obj-$(CONFIG_APDS9960) += apds9960.o obj-$(CONFIG_BH1750) += bh1750.o +obj-$(CONFIG_BH1780) += bh1780.o obj-$(CONFIG_CM32181) += cm32181.o obj-$(CONFIG_CM3232) += cm3232.o obj-$(CONFIG_CM3323) += cm3323.o @@ -20,6 +21,7 @@ obj-$(CONFIG_ISL29125) += isl29125.o obj-$(CONFIG_JSA1212) += jsa1212.o obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o obj-$(CONFIG_LTR501) += ltr501.o +obj-$(CONFIG_MAX44000) += max44000.o obj-$(CONFIG_OPT3001) += opt3001.o obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_RPR0521) += rpr0521.o @@ -30,3 +32,4 @@ obj-$(CONFIG_TCS3472) += tcs3472.o obj-$(CONFIG_TSL4531) += tsl4531.o obj-$(CONFIG_US5182D) += us5182d.o obj-$(CONFIG_VCNL4000) += vcnl4000.o +obj-$(CONFIG_VEML6070) += veml6070.o diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c index 4a6d967..651d57b 100644 --- a/drivers/iio/light/apds9960.c +++ b/drivers/iio/light/apds9960.c @@ -321,8 +321,12 @@ static const struct iio_chan_spec apds9960_channels[] = { }; /* integration time in us */ -static const int apds9960_int_time[][2] = - { {28000, 246}, {100000, 219}, {200000, 182}, {700000, 0} }; +static const int apds9960_int_time[][2] = { + { 28000, 246}, + {100000, 219}, + {200000, 182}, + {700000, 0} +}; /* gain mapping */ static const int apds9960_pxs_gain_map[] = {1, 2, 4, 8}; @@ -491,9 +495,10 @@ static int apds9960_read_raw(struct iio_dev *indio_dev, case IIO_INTENSITY: ret = regmap_bulk_read(data->regmap, chan->address, &buf, 2); - if (!ret) + if (!ret) { ret = IIO_VAL_INT; - *val = le16_to_cpu(buf); + *val = le16_to_cpu(buf); + } break; default: ret = -EINVAL; @@ -769,7 +774,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data) mutex_lock(&data->lock); data->gesture_mode_running = 1; - while (cnt-- || (cnt = apds9660_fifo_is_empty(data) > 0)) { + while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) { ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE, &data->buffer, 4); @@ -777,6 +782,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data) goto err_read; iio_push_to_buffers(data->indio_dev, data->buffer); + cnt--; } err_read: diff --git a/drivers/iio/light/bh1750.c b/drivers/iio/light/bh1750.c index 8b41643..b059466 100644 --- a/drivers/iio/light/bh1750.c +++ b/drivers/iio/light/bh1750.c @@ -241,7 +241,7 @@ static int bh1750_probe(struct i2c_client *client, if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) diff --git b/drivers/iio/light/bh1780.c b/drivers/iio/light/bh1780.c new file mode 100644 index 0000000..b54dcba --- /dev/null +++ b/drivers/iio/light/bh1780.c @@ -0,0 +1,299 @@ +/* + * ROHM 1780GLI Ambient Light Sensor Driver + * + * Copyright (C) 2016 Linaro Ltd. + * Author: Linus Walleij + * Loosely based on the previous BH1780 ALS misc driver + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BH1780_CMD_BIT BIT(7) +#define BH1780_REG_CONTROL 0x00 +#define BH1780_REG_PARTID 0x0A +#define BH1780_REG_MANFID 0x0B +#define BH1780_REG_DLOW 0x0C +#define BH1780_REG_DHIGH 0x0D + +#define BH1780_REVMASK GENMASK(3,0) +#define BH1780_POWMASK GENMASK(1,0) +#define BH1780_POFF (0x0) +#define BH1780_PON (0x3) + +/* power on settling time in ms */ +#define BH1780_PON_DELAY 2 +/* max time before value available in ms */ +#define BH1780_INTERVAL 250 + +struct bh1780_data { + struct i2c_client *client; +}; + +static int bh1780_write(struct bh1780_data *bh1780, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(bh1780->client, + BH1780_CMD_BIT | reg, + val); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_write_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_byte_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_byte_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_read_word(struct bh1780_data *bh1780, u8 reg) +{ + int ret = i2c_smbus_read_word_data(bh1780->client, + BH1780_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1780->client->dev, + "i2c_smbus_read_word_data failed error " + "%d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1780_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + if (!readval) + return bh1780_write(bh1780, (u8)reg, (u8)writeval); + + ret = bh1780_read(bh1780, (u8)reg); + if (ret < 0) + return ret; + + *readval = ret; + + return 0; +} + +static int bh1780_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int value; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + pm_runtime_get_sync(&bh1780->client->dev); + value = bh1780_read_word(bh1780, BH1780_REG_DLOW); + if (value < 0) + return value; + pm_runtime_mark_last_busy(&bh1780->client->dev); + pm_runtime_put_autosuspend(&bh1780->client->dev); + *val = value; + + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = BH1780_INTERVAL * 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static const struct iio_info bh1780_info = { + .driver_module = THIS_MODULE, + .read_raw = bh1780_read_raw, + .debugfs_reg_access = bh1780_debugfs_reg_access, +}; + +static const struct iio_chan_spec bh1780_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME) + } +}; + +static int bh1780_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct bh1780_data *bh1780; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1780)); + if (!indio_dev) + return -ENOMEM; + + bh1780 = iio_priv(indio_dev); + bh1780->client = client; + i2c_set_clientdata(client, indio_dev); + + /* Power up the device */ + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) + return ret; + msleep(BH1780_PON_DELAY); + pm_runtime_get_noresume(&client->dev); + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + + ret = bh1780_read(bh1780, BH1780_REG_PARTID); + if (ret < 0) + goto out_disable_pm; + dev_info(&client->dev, + "Ambient Light Sensor, Rev : %lu\n", + (ret & BH1780_REVMASK)); + + /* + * As the device takes 250 ms to even come up with a fresh + * measurement after power-on, do not shut it down unnecessarily. + * Set autosuspend to a five seconds. + */ + pm_runtime_set_autosuspend_delay(&client->dev, 5000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put(&client->dev); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &bh1780_info; + indio_dev->name = "bh1780"; + indio_dev->channels = bh1780_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1780_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_disable_pm; + return 0; + +out_disable_pm: + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + return ret; +} + +static int bh1780_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + iio_device_unregister(indio_dev); + pm_runtime_get_sync(&client->dev); + pm_runtime_put_noidle(&client->dev); + pm_runtime_disable(&client->dev); + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) { + dev_err(&client->dev, "failed to power off\n"); + return ret; + } + + return 0; +} + +#ifdef CONFIG_PM +static int bh1780_runtime_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF); + if (ret < 0) { + dev_err(dev, "failed to runtime suspend\n"); + return ret; + } + + return 0; +} + +static int bh1780_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1780_data *bh1780 = iio_priv(indio_dev); + int ret; + + ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON); + if (ret < 0) { + dev_err(dev, "failed to runtime resume\n"); + return ret; + } + + /* Wait for power on, then for a value to be available */ + msleep(BH1780_PON_DELAY + BH1780_INTERVAL); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops bh1780_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(bh1780_runtime_suspend, + bh1780_runtime_resume, NULL) +}; + +static const struct i2c_device_id bh1780_id[] = { + { "bh1780", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, bh1780_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_bh1780_match[] = { + { .compatible = "rohm,bh1780gli", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_bh1780_match); +#endif + +static struct i2c_driver bh1780_driver = { + .probe = bh1780_probe, + .remove = bh1780_remove, + .id_table = bh1780_id, + .driver = { + .name = "bh1780", + .pm = &bh1780_dev_pm_ops, + .of_match_table = of_match_ptr(of_bh1780_match), + }, +}; + +module_i2c_driver(bh1780_driver); + +MODULE_DESCRIPTION("ROHM BH1780GLI Ambient Light Sensor Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Linus Walleij "); diff --git a/drivers/iio/light/jsa1212.c b/drivers/iio/light/jsa1212.c index c4e8c6b..99a6281 100644 --- a/drivers/iio/light/jsa1212.c +++ b/drivers/iio/light/jsa1212.c @@ -326,7 +326,7 @@ static int jsa1212_probe(struct i2c_client *client, int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) diff --git a/drivers/iio/light/lm3533-als.c b/drivers/iio/light/lm3533-als.c index 076bc46..e56937c 100644 --- a/drivers/iio/light/lm3533-als.c +++ b/drivers/iio/light/lm3533-als.c @@ -743,8 +743,10 @@ static int lm3533_als_set_resistor(struct lm3533_als *als, u8 val) { int ret; - if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) + if (val < LM3533_ALS_RESISTOR_MIN || val > LM3533_ALS_RESISTOR_MAX) { + dev_err(&als->pdev->dev, "invalid resistor value\n"); return -EINVAL; + }; ret = lm3533_write(als->lm3533, LM3533_REG_ALS_RESISTOR_SELECT, val); if (ret) { diff --git b/drivers/iio/light/max44000.c b/drivers/iio/light/max44000.c new file mode 100644 index 0000000..f17cb2e --- /dev/null +++ b/drivers/iio/light/max44000.c @@ -0,0 +1,638 @@ +/* + * MAX44000 Ambient and Infrared Proximity Sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Data sheet: https://datasheets.maximintegrated.com/en/ds/MAX44000.pdf + * + * 7-bit I2C slave address 0x4a + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX44000_DRV_NAME "max44000" + +/* Registers in datasheet order */ +#define MAX44000_REG_STATUS 0x00 +#define MAX44000_REG_CFG_MAIN 0x01 +#define MAX44000_REG_CFG_RX 0x02 +#define MAX44000_REG_CFG_TX 0x03 +#define MAX44000_REG_ALS_DATA_HI 0x04 +#define MAX44000_REG_ALS_DATA_LO 0x05 +#define MAX44000_REG_PRX_DATA 0x16 +#define MAX44000_REG_ALS_UPTHR_HI 0x06 +#define MAX44000_REG_ALS_UPTHR_LO 0x07 +#define MAX44000_REG_ALS_LOTHR_HI 0x08 +#define MAX44000_REG_ALS_LOTHR_LO 0x09 +#define MAX44000_REG_PST 0x0a +#define MAX44000_REG_PRX_IND 0x0b +#define MAX44000_REG_PRX_THR 0x0c +#define MAX44000_REG_TRIM_GAIN_GREEN 0x0f +#define MAX44000_REG_TRIM_GAIN_IR 0x10 + +/* REG_CFG bits */ +#define MAX44000_CFG_ALSINTE 0x01 +#define MAX44000_CFG_PRXINTE 0x02 +#define MAX44000_CFG_MASK 0x1c +#define MAX44000_CFG_MODE_SHUTDOWN 0x00 +#define MAX44000_CFG_MODE_ALS_GIR 0x04 +#define MAX44000_CFG_MODE_ALS_G 0x08 +#define MAX44000_CFG_MODE_ALS_IR 0x0c +#define MAX44000_CFG_MODE_ALS_PRX 0x10 +#define MAX44000_CFG_MODE_PRX 0x14 +#define MAX44000_CFG_TRIM 0x20 + +/* + * Upper 4 bits are not documented but start as 1 on powerup + * Setting them to 0 causes proximity to misbehave so set them to 1 + */ +#define MAX44000_REG_CFG_RX_DEFAULT 0xf0 + +/* REG_RX bits */ +#define MAX44000_CFG_RX_ALSTIM_MASK 0x0c +#define MAX44000_CFG_RX_ALSTIM_SHIFT 2 +#define MAX44000_CFG_RX_ALSPGA_MASK 0x03 +#define MAX44000_CFG_RX_ALSPGA_SHIFT 0 + +/* REG_TX bits */ +#define MAX44000_LED_CURRENT_MASK 0xf +#define MAX44000_LED_CURRENT_MAX 11 +#define MAX44000_LED_CURRENT_DEFAULT 6 + +#define MAX44000_ALSDATA_OVERFLOW 0x4000 + +struct max44000_data { + struct mutex lock; + struct regmap *regmap; +}; + +/* Default scale is set to the minimum of 0.03125 or 1 / (1 << 5) lux */ +#define MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 5 + +/* Scale can be multiplied by up to 128x via ALSPGA for measurement gain */ +static const int max44000_alspga_shift[] = {0, 2, 4, 7}; +#define MAX44000_ALSPGA_MAX_SHIFT 7 + +/* + * Scale can be multiplied by up to 64x via ALSTIM because of lost resolution + * + * This scaling factor is hidden from userspace and instead accounted for when + * reading raw values from the device. + * + * This makes it possible to cleanly expose ALSPGA as IIO_CHAN_INFO_SCALE and + * ALSTIM as IIO_CHAN_INFO_INT_TIME without the values affecting each other. + * + * Handling this internally is also required for buffer support because the + * channel's scan_type can't be modified dynamically. + */ +static const int max44000_alstim_shift[] = {0, 2, 4, 6}; +#define MAX44000_ALSTIM_SHIFT(alstim) (2 * (alstim)) + +/* Available integration times with pretty manual alignment: */ +static const int max44000_int_time_avail_ns_array[] = { + 100000000, + 25000000, + 6250000, + 1562500, +}; +static const char max44000_int_time_avail_str[] = + "0.100 " + "0.025 " + "0.00625 " + "0.001625"; + +/* Available scales (internal to ulux) with pretty manual alignment: */ +static const int max44000_scale_avail_ulux_array[] = { + 31250, + 125000, + 500000, + 4000000, +}; +static const char max44000_scale_avail_str[] = + "0.03125 " + "0.125 " + "0.5 " + "4"; + +#define MAX44000_SCAN_INDEX_ALS 0 +#define MAX44000_SCAN_INDEX_PRX 1 + +static const struct iio_chan_spec max44000_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_INT_TIME), + .scan_index = MAX44000_SCAN_INDEX_ALS, + .scan_type = { + .sign = 'u', + .realbits = 14, + .storagebits = 16, + } + }, + { + .type = IIO_PROXIMITY, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .scan_index = MAX44000_SCAN_INDEX_PRX, + .scan_type = { + .sign = 'u', + .realbits = 8, + .storagebits = 16, + } + }, + IIO_CHAN_SOFT_TIMESTAMP(2), + { + .type = IIO_CURRENT, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .extend_name = "led", + .output = 1, + .scan_index = -1, + }, +}; + +static int max44000_read_alstim(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSTIM_MASK) >> MAX44000_CFG_RX_ALSTIM_SHIFT; +} + +static int max44000_write_alstim(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSTIM_MASK, + val << MAX44000_CFG_RX_ALSTIM_SHIFT); +} + +static int max44000_read_alspga(struct max44000_data *data) +{ + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_RX, &val); + if (ret < 0) + return ret; + return (val & MAX44000_CFG_RX_ALSPGA_MASK) >> MAX44000_CFG_RX_ALSPGA_SHIFT; +} + +static int max44000_write_alspga(struct max44000_data *data, int val) +{ + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_CFG_RX_ALSPGA_MASK, + val << MAX44000_CFG_RX_ALSPGA_SHIFT); +} + +static int max44000_read_alsval(struct max44000_data *data) +{ + u16 regval; + int alstim, ret; + + ret = regmap_bulk_read(data->regmap, MAX44000_REG_ALS_DATA_HI, + ®val, sizeof(regval)); + if (ret < 0) + return ret; + alstim = ret = max44000_read_alstim(data); + if (ret < 0) + return ret; + + regval = be16_to_cpu(regval); + + /* + * Overflow is explained on datasheet page 17. + * + * It's a warning that either the G or IR channel has become saturated + * and that the value in the register is likely incorrect. + * + * The recommendation is to change the scale (ALSPGA). + * The driver just returns the max representable value. + */ + if (regval & MAX44000_ALSDATA_OVERFLOW) + return 0x3FFF; + + return regval << MAX44000_ALSTIM_SHIFT(alstim); +} + +static int max44000_write_led_current_raw(struct max44000_data *data, int val) +{ + /* Maybe we should clamp the value instead? */ + if (val < 0 || val > MAX44000_LED_CURRENT_MAX) + return -ERANGE; + if (val >= 8) + val += 4; + return regmap_write_bits(data->regmap, MAX44000_REG_CFG_TX, + MAX44000_LED_CURRENT_MASK, val); +} + +static int max44000_read_led_current_raw(struct max44000_data *data) +{ + unsigned int regval; + int ret; + + ret = regmap_read(data->regmap, MAX44000_REG_CFG_TX, ®val); + if (ret < 0) + return ret; + regval &= MAX44000_LED_CURRENT_MASK; + if (regval >= 8) + regval -= 4; + return regval; +} + +static int max44000_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int alstim, alspga; + unsigned int regval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_LIGHT: + mutex_lock(&data->lock); + ret = max44000_read_alsval(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + case IIO_PROXIMITY: + mutex_lock(&data->lock); + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = regval; + return IIO_VAL_INT; + + case IIO_CURRENT: + mutex_lock(&data->lock); + ret = max44000_read_led_current_raw(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_CURRENT: + /* Output register is in 10s of miliamps */ + *val = 10; + return IIO_VAL_INT; + + case IIO_LIGHT: + mutex_lock(&data->lock); + alspga = ret = max44000_read_alspga(data); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + /* Avoid negative shifts */ + *val = (1 << MAX44000_ALSPGA_MAX_SHIFT); + *val2 = MAX44000_ALS_TO_LUX_DEFAULT_FRACTION_LOG2 + + MAX44000_ALSPGA_MAX_SHIFT + - max44000_alspga_shift[alspga]; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } + + case IIO_CHAN_INFO_INT_TIME: + mutex_lock(&data->lock); + alstim = ret = max44000_read_alstim(data); + mutex_unlock(&data->lock); + + if (ret < 0) + return ret; + *val = 0; + *val2 = max44000_int_time_avail_ns_array[alstim]; + return IIO_VAL_INT_PLUS_NANO; + + default: + return -EINVAL; + } +} + +static int max44000_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct max44000_data *data = iio_priv(indio_dev); + int ret; + + if (mask == IIO_CHAN_INFO_RAW && chan->type == IIO_CURRENT) { + mutex_lock(&data->lock); + ret = max44000_write_led_current_raw(data, val); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) { + s64 valns = val * NSEC_PER_SEC + val2; + int alstim = find_closest_descending(valns, + max44000_int_time_avail_ns_array, + ARRAY_SIZE(max44000_int_time_avail_ns_array)); + mutex_lock(&data->lock); + ret = max44000_write_alstim(data, alstim); + mutex_unlock(&data->lock); + return ret; + } else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) { + s64 valus = val * USEC_PER_SEC + val2; + int alspga = find_closest(valus, + max44000_scale_avail_ulux_array, + ARRAY_SIZE(max44000_scale_avail_ulux_array)); + mutex_lock(&data->lock); + ret = max44000_write_alspga(data, alspga); + mutex_unlock(&data->lock); + return ret; + } + + return -EINVAL; +} + +static int max44000_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (mask == IIO_CHAN_INFO_INT_TIME && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_NANO; + else if (mask == IIO_CHAN_INFO_SCALE && chan->type == IIO_LIGHT) + return IIO_VAL_INT_PLUS_MICRO; + else + return IIO_VAL_INT; +} + +static IIO_CONST_ATTR(illuminance_integration_time_available, max44000_int_time_avail_str); +static IIO_CONST_ATTR(illuminance_scale_available, max44000_scale_avail_str); + +static struct attribute *max44000_attributes[] = { + &iio_const_attr_illuminance_integration_time_available.dev_attr.attr, + &iio_const_attr_illuminance_scale_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group max44000_attribute_group = { + .attrs = max44000_attributes, +}; + +static const struct iio_info max44000_info = { + .driver_module = THIS_MODULE, + .read_raw = max44000_read_raw, + .write_raw = max44000_write_raw, + .write_raw_get_fmt = max44000_write_raw_get_fmt, + .attrs = &max44000_attribute_group, +}; + +static bool max44000_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_CFG_MAIN: + case MAX44000_REG_CFG_RX: + case MAX44000_REG_CFG_TX: + case MAX44000_REG_ALS_UPTHR_HI: + case MAX44000_REG_ALS_UPTHR_LO: + case MAX44000_REG_ALS_LOTHR_HI: + case MAX44000_REG_ALS_LOTHR_LO: + case MAX44000_REG_PST: + case MAX44000_REG_PRX_IND: + case MAX44000_REG_PRX_THR: + case MAX44000_REG_TRIM_GAIN_GREEN: + case MAX44000_REG_TRIM_GAIN_IR: + return true; + default: + return false; + } +} + +static bool max44000_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX44000_REG_STATUS: + case MAX44000_REG_ALS_DATA_HI: + case MAX44000_REG_ALS_DATA_LO: + case MAX44000_REG_PRX_DATA: + return true; + default: + return false; + } +} + +static bool max44000_precious_reg(struct device *dev, unsigned int reg) +{ + return reg == MAX44000_REG_STATUS; +} + +static const struct regmap_config max44000_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = MAX44000_REG_PRX_DATA, + .readable_reg = max44000_readable_reg, + .writeable_reg = max44000_writeable_reg, + .volatile_reg = max44000_volatile_reg, + .precious_reg = max44000_precious_reg, + + .use_single_rw = 1, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t max44000_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct max44000_data *data = iio_priv(indio_dev); + u16 buf[8]; /* 2x u16 + padding + 8 bytes timestamp */ + int index = 0; + unsigned int regval; + int ret; + + mutex_lock(&data->lock); + if (test_bit(MAX44000_SCAN_INDEX_ALS, indio_dev->active_scan_mask)) { + ret = max44000_read_alsval(data); + if (ret < 0) + goto out_unlock; + buf[index++] = ret; + } + if (test_bit(MAX44000_SCAN_INDEX_PRX, indio_dev->active_scan_mask)) { + ret = regmap_read(data->regmap, MAX44000_REG_PRX_DATA, ®val); + if (ret < 0) + goto out_unlock; + buf[index] = regval; + } + mutex_unlock(&data->lock); + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; + +out_unlock: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +static int max44000_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max44000_data *data; + struct iio_dev *indio_dev; + int ret, reg; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + data->regmap = devm_regmap_init_i2c(client, &max44000_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(&client->dev, "regmap_init failed!\n"); + return PTR_ERR(data->regmap); + } + + i2c_set_clientdata(client, indio_dev); + mutex_init(&data->lock); + indio_dev->dev.parent = &client->dev; + indio_dev->info = &max44000_info; + indio_dev->name = MAX44000_DRV_NAME; + indio_dev->channels = max44000_channels; + indio_dev->num_channels = ARRAY_SIZE(max44000_channels); + + /* + * The device doesn't have a reset function so we just clear some + * important bits at probe time to ensure sane operation. + * + * Since we don't support interrupts/events the threshold values are + * not important. We also don't touch trim values. + */ + + /* Reset ALS scaling bits */ + ret = regmap_write(data->regmap, MAX44000_REG_CFG_RX, + MAX44000_REG_CFG_RX_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write default CFG_RX: %d\n", + ret); + return ret; + } + + /* + * By default the LED pulse used for the proximity sensor is disabled. + * Set a middle value so that we get some sort of valid data by default. + */ + ret = max44000_write_led_current_raw(data, MAX44000_LED_CURRENT_DEFAULT); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Reset CFG bits to ALS_PRX mode which allows easy reading of both values. */ + reg = MAX44000_CFG_TRIM | MAX44000_CFG_MODE_ALS_PRX; + ret = regmap_write(data->regmap, MAX44000_REG_CFG_MAIN, reg); + if (ret < 0) { + dev_err(&client->dev, "failed to write init config: %d\n", ret); + return ret; + } + + /* Read status at least once to clear any stale interrupt bits. */ + ret = regmap_read(data->regmap, MAX44000_REG_STATUS, ®); + if (ret < 0) { + dev_err(&client->dev, "failed to read init status: %d\n", ret); + return ret; + } + + ret = iio_triggered_buffer_setup(indio_dev, NULL, max44000_trigger_handler, NULL); + if (ret < 0) { + dev_err(&client->dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return iio_device_register(indio_dev); +} + +static int max44000_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + return 0; +} + +static const struct i2c_device_id max44000_id[] = { + {"max44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, max44000_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id max44000_acpi_match[] = { + {"MAX44000", 0}, + { } +}; +MODULE_DEVICE_TABLE(acpi, max44000_acpi_match); +#endif + +static struct i2c_driver max44000_driver = { + .driver = { + .name = MAX44000_DRV_NAME, + .acpi_match_table = ACPI_PTR(max44000_acpi_match), + }, + .probe = max44000_probe, + .remove = max44000_remove, + .id_table = max44000_id, +}; + +module_i2c_driver(max44000_driver); + +MODULE_AUTHOR("Crestez Dan Leonard "); +MODULE_DESCRIPTION("MAX44000 Ambient and Infrared Proximity Sensor"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/light/opt3001.c b/drivers/iio/light/opt3001.c index 01e111e..b776c8e 100644 --- a/drivers/iio/light/opt3001.c +++ b/drivers/iio/light/opt3001.c @@ -65,19 +65,25 @@ #define OPT3001_REG_EXPONENT(n) ((n) >> 12) #define OPT3001_REG_MANTISSA(n) ((n) & 0xfff) +#define OPT3001_INT_TIME_LONG 800000 +#define OPT3001_INT_TIME_SHORT 100000 + /* * Time to wait for conversion result to be ready. The device datasheet - * worst-case max value is 880ms. Add some slack to be on the safe side. + * sect. 6.5 states results are ready after total integration time plus 3ms. + * This results in worst-case max values of 113ms or 883ms, respectively. + * Add some slack to be on the safe side. */ -#define OPT3001_RESULT_READY_TIMEOUT msecs_to_jiffies(1000) +#define OPT3001_RESULT_READY_SHORT 150 +#define OPT3001_RESULT_READY_LONG 1000 struct opt3001 { struct i2c_client *client; struct device *dev; struct mutex lock; - u16 ok_to_ignore_lock:1; - u16 result_ready:1; + bool ok_to_ignore_lock; + bool result_ready; wait_queue_head_t result_ready_queue; u16 result; @@ -89,6 +95,8 @@ struct opt3001 { u8 high_thresh_exp; u8 low_thresh_exp; + + bool use_irq; }; struct opt3001_scale { @@ -227,26 +235,30 @@ static int opt3001_get_lux(struct opt3001 *opt, int *val, int *val2) u16 reg; u8 exponent; u16 value; + long timeout; - /* - * Enable the end-of-conversion interrupt mechanism. Note that doing - * so will overwrite the low-level limit value however we will restore - * this value later on. - */ - ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT, - OPT3001_LOW_LIMIT_EOC_ENABLE); - if (ret < 0) { - dev_err(opt->dev, "failed to write register %02x\n", - OPT3001_LOW_LIMIT); - return ret; + if (opt->use_irq) { + /* + * Enable the end-of-conversion interrupt mechanism. Note that + * doing so will overwrite the low-level limit value however we + * will restore this value later on. + */ + ret = i2c_smbus_write_word_swapped(opt->client, + OPT3001_LOW_LIMIT, + OPT3001_LOW_LIMIT_EOC_ENABLE); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } + + /* Allow IRQ to access the device despite lock being set */ + opt->ok_to_ignore_lock = true; } - /* Reset data-ready indicator flag (will be set in the IRQ routine) */ + /* Reset data-ready indicator flag */ opt->result_ready = false; - /* Allow IRQ to access the device despite lock being set */ - opt->ok_to_ignore_lock = true; - /* Configure for single-conversion mode and start a new conversion */ ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); if (ret < 0) { @@ -266,32 +278,69 @@ static int opt3001_get_lux(struct opt3001 *opt, int *val, int *val2) goto err; } - /* Wait for the IRQ to indicate the conversion is complete */ - ret = wait_event_timeout(opt->result_ready_queue, opt->result_ready, - OPT3001_RESULT_READY_TIMEOUT); + if (opt->use_irq) { + /* Wait for the IRQ to indicate the conversion is complete */ + ret = wait_event_timeout(opt->result_ready_queue, + opt->result_ready, + msecs_to_jiffies(OPT3001_RESULT_READY_LONG)); + } else { + /* Sleep for result ready time */ + timeout = (opt->int_time == OPT3001_INT_TIME_SHORT) ? + OPT3001_RESULT_READY_SHORT : OPT3001_RESULT_READY_LONG; + msleep(timeout); + + /* Check result ready flag */ + ret = i2c_smbus_read_word_swapped(opt->client, + OPT3001_CONFIGURATION); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_CONFIGURATION); + goto err; + } + + if (!(ret & OPT3001_CONFIGURATION_CRF)) { + ret = -ETIMEDOUT; + goto err; + } + + /* Obtain value */ + ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_RESULT); + if (ret < 0) { + dev_err(opt->dev, "failed to read register %02x\n", + OPT3001_RESULT); + goto err; + } + opt->result = ret; + opt->result_ready = true; + } err: - /* Disallow IRQ to access the device while lock is active */ - opt->ok_to_ignore_lock = false; + if (opt->use_irq) + /* Disallow IRQ to access the device while lock is active */ + opt->ok_to_ignore_lock = false; if (ret == 0) return -ETIMEDOUT; else if (ret < 0) return ret; - /* - * Disable the end-of-conversion interrupt mechanism by restoring the - * low-level limit value (clearing OPT3001_LOW_LIMIT_EOC_ENABLE). Note - * that selectively clearing those enable bits would affect the actual - * limit value due to bit-overlap and therefore can't be done. - */ - value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa; - ret = i2c_smbus_write_word_swapped(opt->client, OPT3001_LOW_LIMIT, - value); - if (ret < 0) { - dev_err(opt->dev, "failed to write register %02x\n", - OPT3001_LOW_LIMIT); - return ret; + if (opt->use_irq) { + /* + * Disable the end-of-conversion interrupt mechanism by + * restoring the low-level limit value (clearing + * OPT3001_LOW_LIMIT_EOC_ENABLE). Note that selectively clearing + * those enable bits would affect the actual limit value due to + * bit-overlap and therefore can't be done. + */ + value = (opt->low_thresh_exp << 12) | opt->low_thresh_mantissa; + ret = i2c_smbus_write_word_swapped(opt->client, + OPT3001_LOW_LIMIT, + value); + if (ret < 0) { + dev_err(opt->dev, "failed to write register %02x\n", + OPT3001_LOW_LIMIT); + return ret; + } } exponent = OPT3001_REG_EXPONENT(opt->result); @@ -325,13 +374,13 @@ static int opt3001_set_int_time(struct opt3001 *opt, int time) reg = ret; switch (time) { - case 100000: + case OPT3001_INT_TIME_SHORT: reg &= ~OPT3001_CONFIGURATION_CT; - opt->int_time = 100000; + opt->int_time = OPT3001_INT_TIME_SHORT; break; - case 800000: + case OPT3001_INT_TIME_LONG: reg |= OPT3001_CONFIGURATION_CT; - opt->int_time = 800000; + opt->int_time = OPT3001_INT_TIME_LONG; break; default: return -EINVAL; @@ -597,9 +646,9 @@ static int opt3001_configure(struct opt3001 *opt) /* Reflect status of the device's integration time setting */ if (reg & OPT3001_CONFIGURATION_CT) - opt->int_time = 800000; + opt->int_time = OPT3001_INT_TIME_LONG; else - opt->int_time = 100000; + opt->int_time = OPT3001_INT_TIME_SHORT; /* Ensure device is in shutdown initially */ opt3001_set_mode(opt, ®, OPT3001_CONFIGURATION_M_SHUTDOWN); @@ -733,12 +782,18 @@ static int opt3001_probe(struct i2c_client *client, return ret; } - ret = request_threaded_irq(irq, NULL, opt3001_irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - "opt3001", iio); - if (ret) { - dev_err(dev, "failed to request IRQ #%d\n", irq); - return ret; + /* Make use of INT pin only if valid IRQ no. is given */ + if (irq > 0) { + ret = request_threaded_irq(irq, NULL, opt3001_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "opt3001", iio); + if (ret) { + dev_err(dev, "failed to request IRQ #%d\n", irq); + return ret; + } + opt->use_irq = true; + } else { + dev_dbg(opt->dev, "enabling interrupt-less operation\n"); } return 0; @@ -751,7 +806,8 @@ static int opt3001_remove(struct i2c_client *client) int ret; u16 reg; - free_irq(client->irq, iio); + if (opt->use_irq) + free_irq(client->irq, iio); ret = i2c_smbus_read_word_swapped(opt->client, OPT3001_CONFIGURATION); if (ret < 0) { diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c index 45f7bde..76a9e12 100644 --- a/drivers/iio/light/pa12203001.c +++ b/drivers/iio/light/pa12203001.c @@ -381,17 +381,23 @@ static int pa12203001_probe(struct i2c_client *client, return ret; ret = pm_runtime_set_active(&client->dev); - if (ret < 0) { - pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); - return ret; - } + if (ret < 0) + goto out_err; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, PA12203001_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - return iio_device_register(indio_dev); + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + pa12203001_power_chip(indio_dev, PA12203001_CHIP_DISABLE); + return ret; } static int pa12203001_remove(struct i2c_client *client) diff --git a/drivers/iio/light/rpr0521.c b/drivers/iio/light/rpr0521.c index 4b75bb0..7de0f39 100644 --- a/drivers/iio/light/rpr0521.c +++ b/drivers/iio/light/rpr0521.c @@ -507,34 +507,28 @@ static int rpr0521_probe(struct i2c_client *client, dev_err(&client->dev, "rpr0521 chip init failed\n"); return ret; } - ret = iio_device_register(indio_dev); - if (ret < 0) - return ret; ret = pm_runtime_set_active(&client->dev); if (ret < 0) - goto err_iio_unregister; + return ret; pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, RPR0521_SLEEP_DELAY_MS); pm_runtime_use_autosuspend(&client->dev); - return 0; - -err_iio_unregister: - iio_device_unregister(indio_dev); - return ret; + return iio_device_register(indio_dev); } static int rpr0521_remove(struct i2c_client *client) { struct iio_dev *indio_dev = i2c_get_clientdata(client); + iio_device_unregister(indio_dev); + pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); rpr0521_poweroff(iio_priv(indio_dev)); return 0; diff --git a/drivers/iio/light/stk3310.c b/drivers/iio/light/stk3310.c index 42d334b..9e847f8 100644 --- a/drivers/iio/light/stk3310.c +++ b/drivers/iio/light/stk3310.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/iio/light/tsl2563.c b/drivers/iio/light/tsl2563.c index 12731d6..57b108c 100644 --- a/drivers/iio/light/tsl2563.c +++ b/drivers/iio/light/tsl2563.c @@ -806,8 +806,7 @@ static int tsl2563_probe(struct i2c_client *client, return 0; fail: - cancel_delayed_work(&chip->poweroff_work); - flush_scheduled_work(); + cancel_delayed_work_sync(&chip->poweroff_work); return err; } diff --git a/drivers/iio/light/us5182d.c b/drivers/iio/light/us5182d.c index 49dab3c..45bc2f7 100644 --- a/drivers/iio/light/us5182d.c +++ b/drivers/iio/light/us5182d.c @@ -20,14 +20,21 @@ #include #include #include +#include #include +#include +#include #include #include +#include +#include #define US5182D_REG_CFG0 0x00 #define US5182D_CFG0_ONESHOT_EN BIT(6) #define US5182D_CFG0_SHUTDOWN_EN BIT(7) #define US5182D_CFG0_WORD_ENABLE BIT(0) +#define US5182D_CFG0_PROX BIT(3) +#define US5182D_CFG0_PX_IRQ BIT(2) #define US5182D_REG_CFG1 0x01 #define US5182D_CFG1_ALS_RES16 BIT(4) @@ -39,6 +46,7 @@ #define US5182D_REG_CFG3 0x03 #define US5182D_CFG3_LED_CURRENT100 (BIT(4) | BIT(5)) +#define US5182D_CFG3_INT_SOURCE_PX BIT(3) #define US5182D_REG_CFG4 0x10 @@ -53,6 +61,13 @@ #define US5182D_REG_AUTO_LDARK_GAIN 0x29 #define US5182D_REG_AUTO_HDARK_GAIN 0x2a +/* Thresholds for events: px low (0x08-l, 0x09-h), px high (0x0a-l 0x0b-h) */ +#define US5182D_REG_PXL_TH 0x08 +#define US5182D_REG_PXH_TH 0x0a + +#define US5182D_REG_PXL_TH_DEFAULT 1000 +#define US5182D_REG_PXH_TH_DEFAULT 30000 + #define US5182D_OPMODE_ALS 0x01 #define US5182D_OPMODE_PX 0x02 #define US5182D_OPMODE_SHIFT 4 @@ -81,6 +96,9 @@ #define US5182D_READ_BYTE 1 #define US5182D_READ_WORD 2 #define US5182D_OPSTORE_SLEEP_TIME 20 /* ms */ +#define US5182D_SLEEP_MS 3000 /* ms */ +#define US5182D_PXH_TH_DISABLE 0xffff +#define US5182D_PXL_TH_DISABLE 0x0000 /* Available ranges: [12354, 7065, 3998, 2202, 1285, 498, 256, 138] lux */ static const int us5182d_scales[] = {188500, 107800, 61000, 33600, 19600, 7600, @@ -99,6 +117,11 @@ enum mode { US5182D_PX_ONLY }; +enum pmode { + US5182D_CONTINUOUS, + US5182D_ONESHOT +}; + struct us5182d_data { struct i2c_client *client; struct mutex lock; @@ -111,7 +134,19 @@ struct us5182d_data { u8 upper_dark_gain; u16 *us5182d_dark_ths; + u16 px_low_th; + u16 px_high_th; + + int rising_en; + int falling_en; + u8 opmode; + u8 power_mode; + + bool als_enabled; + bool px_enabled; + + bool default_continuous; }; static IIO_CONST_ATTR(in_illuminance_scale_available, @@ -130,16 +165,30 @@ static const struct { u8 reg; u8 val; } us5182d_regvals[] = { - {US5182D_REG_CFG0, (US5182D_CFG0_SHUTDOWN_EN | - US5182D_CFG0_WORD_ENABLE)}, + {US5182D_REG_CFG0, US5182D_CFG0_WORD_ENABLE}, {US5182D_REG_CFG1, US5182D_CFG1_ALS_RES16}, {US5182D_REG_CFG2, (US5182D_CFG2_PX_RES16 | US5182D_CFG2_PXGAIN_DEFAULT)}, - {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100}, - {US5182D_REG_MODE_STORE, US5182D_STORE_MODE}, + {US5182D_REG_CFG3, US5182D_CFG3_LED_CURRENT100 | + US5182D_CFG3_INT_SOURCE_PX}, {US5182D_REG_CFG4, 0x00}, }; +static const struct iio_event_spec us5182d_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_FALLING, + .mask_separate = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, +}; + static const struct iio_chan_spec us5182d_channels[] = { { .type = IIO_LIGHT, @@ -149,40 +198,39 @@ static const struct iio_chan_spec us5182d_channels[] = { { .type = IIO_PROXIMITY, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .event_spec = us5182d_events, + .num_event_specs = ARRAY_SIZE(us5182d_events), } }; -static int us5182d_get_als(struct us5182d_data *data) +static int us5182d_oneshot_en(struct us5182d_data *data) { int ret; - unsigned long result; - ret = i2c_smbus_read_word_data(data->client, - US5182D_REG_ADL); + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); if (ret < 0) return ret; - result = ret * data->ga / US5182D_GA_RESOLUTION; - if (result > 0xffff) - result = 0xffff; + /* + * In oneshot mode the chip will power itself down after taking the + * required measurement. + */ + ret = ret | US5182D_CFG0_ONESHOT_EN; - return result; + return i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); } static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) { int ret; + if (mode == data->opmode) + return 0; + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); if (ret < 0) return ret; - /* - * In oneshot mode the chip will power itself down after taking the - * required measurement. - */ - ret = ret | US5182D_CFG0_ONESHOT_EN; - /* update mode */ ret = ret & ~US5182D_OPMODE_MASK; ret = ret | (mode << US5182D_OPMODE_SHIFT); @@ -196,9 +244,6 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) if (ret < 0) return ret; - if (mode == data->opmode) - return 0; - ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_MODE_STORE, US5182D_STORE_MODE); if (ret < 0) @@ -210,6 +255,177 @@ static int us5182d_set_opmode(struct us5182d_data *data, u8 mode) return 0; } +static int us5182d_als_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_ALS_ONLY); + if (ret < 0) + return ret; + data->px_enabled = false; + } + + if (data->als_enabled) + return 0; + + mode = data->px_enabled ? US5182D_ALS_PX : US5182D_ALS_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->als_enabled = true; + + return 0; +} + +static int us5182d_px_enable(struct us5182d_data *data) +{ + int ret; + u8 mode; + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_set_opmode(data, US5182D_PX_ONLY); + if (ret < 0) + return ret; + data->als_enabled = false; + } + + if (data->px_enabled) + return 0; + + mode = data->als_enabled ? US5182D_ALS_PX : US5182D_PX_ONLY; + + ret = us5182d_set_opmode(data, mode); + if (ret < 0) + return ret; + + data->px_enabled = true; + + return 0; +} + +static int us5182d_get_als(struct us5182d_data *data) +{ + int ret; + unsigned long result; + + ret = us5182d_als_enable(data); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(data->client, + US5182D_REG_ADL); + if (ret < 0) + return ret; + + result = ret * data->ga / US5182D_GA_RESOLUTION; + if (result > 0xffff) + result = 0xffff; + + return result; +} + +static int us5182d_get_px(struct us5182d_data *data) +{ + int ret; + + ret = us5182d_px_enable(data); + if (ret < 0) + return ret; + + return i2c_smbus_read_word_data(data->client, + US5182D_REG_PDL); +} + +static int us5182d_shutdown_en(struct us5182d_data *data, u8 state) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) + return ret; + + ret = ret & ~US5182D_CFG0_SHUTDOWN_EN; + ret = ret | state; + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, ret); + if (ret < 0) + return ret; + + if (state & US5182D_CFG0_SHUTDOWN_EN) { + data->als_enabled = false; + data->px_enabled = false; + } + + return ret; +} + + +static int us5182d_set_power_state(struct us5182d_data *data, bool on) +{ + int ret; + + if (data->power_mode == US5182D_ONESHOT) + return 0; + + if (on) { + ret = pm_runtime_get_sync(&data->client->dev); + if (ret < 0) + pm_runtime_put_noidle(&data->client->dev); + } else { + pm_runtime_mark_last_busy(&data->client->dev); + ret = pm_runtime_put_autosuspend(&data->client->dev); + } + + return ret; +} + +static int us5182d_read_value(struct us5182d_data *data, + struct iio_chan_spec const *chan) +{ + int ret, value; + + mutex_lock(&data->lock); + + if (data->power_mode == US5182D_ONESHOT) { + ret = us5182d_oneshot_en(data); + if (ret < 0) + goto out_err; + } + + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto out_err; + + if (chan->type == IIO_LIGHT) + ret = us5182d_get_als(data); + else + ret = us5182d_get_px(data); + if (ret < 0) + goto out_poweroff; + + value = ret; + + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto out_err; + + mutex_unlock(&data->lock); + return value; + +out_poweroff: + us5182d_set_power_state(data, false); +out_err: + mutex_unlock(&data->lock); + return ret; +} + static int us5182d_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -219,53 +435,21 @@ static int us5182d_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - switch (chan->type) { - case IIO_LIGHT: - mutex_lock(&data->lock); - ret = us5182d_set_opmode(data, US5182D_OPMODE_ALS); - if (ret < 0) - goto out_err; - - ret = us5182d_get_als(data); - if (ret < 0) - goto out_err; - mutex_unlock(&data->lock); - *val = ret; - return IIO_VAL_INT; - case IIO_PROXIMITY: - mutex_lock(&data->lock); - ret = us5182d_set_opmode(data, US5182D_OPMODE_PX); - if (ret < 0) - goto out_err; - - ret = i2c_smbus_read_word_data(data->client, - US5182D_REG_PDL); - if (ret < 0) - goto out_err; - mutex_unlock(&data->lock); - *val = ret; - return IIO_VAL_INT; - default: - return -EINVAL; - } - + ret = us5182d_read_value(data, chan); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG1); if (ret < 0) return ret; - *val = 0; *val2 = us5182d_scales[ret & US5182D_AGAIN_MASK]; - return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } - - return -EINVAL; -out_err: - mutex_unlock(&data->lock); - return ret; } /** @@ -343,11 +527,201 @@ static int us5182d_write_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int us5182d_setup_prox(struct iio_dev *indio_dev, + enum iio_event_direction dir, u16 val) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + if (dir == IIO_EV_DIR_FALLING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXL_TH, val); + else if (dir == IIO_EV_DIR_RISING) + return i2c_smbus_write_word_data(data->client, + US5182D_REG_PXH_TH, val); + + return 0; +} + +static int us5182d_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int *val, + int *val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + *val = data->px_high_th; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + *val = data->px_low_th; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int us5182d_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, enum iio_event_info info, int val, + int val2) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + if (val < 0 || val > USHRT_MAX || val2 != 0) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + if (data->rising_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_high_th = val; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + if (data->falling_en) { + ret = us5182d_setup_prox(indio_dev, dir, val); + if (ret < 0) + goto err; + } + data->px_low_th = val; + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return 0; +err: + mutex_unlock(&data->lock); + return ret; +} + +static int us5182d_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + + switch (dir) { + case IIO_EV_DIR_RISING: + mutex_lock(&data->lock); + ret = data->rising_en; + mutex_unlock(&data->lock); + break; + case IIO_EV_DIR_FALLING: + mutex_lock(&data->lock); + ret = data->falling_en; + mutex_unlock(&data->lock); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int us5182d_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct us5182d_data *data = iio_priv(indio_dev); + int ret; + u16 new_th; + + mutex_lock(&data->lock); + + switch (dir) { + case IIO_EV_DIR_RISING: + if (data->rising_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXH_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_high_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->rising_en = state; + break; + case IIO_EV_DIR_FALLING: + if (data->falling_en == state) { + mutex_unlock(&data->lock); + return 0; + } + new_th = US5182D_PXL_TH_DISABLE; + if (state) { + data->power_mode = US5182D_CONTINUOUS; + ret = us5182d_set_power_state(data, true); + if (ret < 0) + goto err; + ret = us5182d_px_enable(data); + if (ret < 0) + goto err_poweroff; + new_th = data->px_low_th; + } + ret = us5182d_setup_prox(indio_dev, dir, new_th); + if (ret < 0) + goto err_poweroff; + data->falling_en = state; + break; + default: + ret = -EINVAL; + goto err; + } + + if (!state) { + ret = us5182d_set_power_state(data, false); + if (ret < 0) + goto err; + } + + if (!data->falling_en && !data->rising_en && !data->default_continuous) + data->power_mode = US5182D_ONESHOT; + + mutex_unlock(&data->lock); + return 0; + +err_poweroff: + if (state) + us5182d_set_power_state(data, false); +err: + mutex_unlock(&data->lock); + return ret; +} + static const struct iio_info us5182d_info = { .driver_module = THIS_MODULE, .read_raw = us5182d_read_raw, .write_raw = us5182d_write_raw, .attrs = &us5182d_attr_group, + .read_event_value = &us5182d_read_thresh, + .write_event_value = &us5182d_write_thresh, + .read_event_config = &us5182d_read_event_config, + .write_event_config = &us5182d_write_event_config, }; static int us5182d_reset(struct iio_dev *indio_dev) @@ -368,6 +742,10 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; data->opmode = 0; + data->power_mode = US5182D_CONTINUOUS; + data->px_low_th = US5182D_REG_PXL_TH_DEFAULT; + data->px_high_th = US5182D_REG_PXH_TH_DEFAULT; + for (i = 0; i < ARRAY_SIZE(us5182d_regvals); i++) { ret = i2c_smbus_write_byte_data(data->client, us5182d_regvals[i].reg, @@ -376,7 +754,17 @@ static int us5182d_init(struct iio_dev *indio_dev) return ret; } - return 0; + data->als_enabled = true; + data->px_enabled = true; + + if (!data->default_continuous) { + ret = us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + if (ret < 0) + return ret; + data->power_mode = US5182D_ONESHOT; + } + + return ret; } static void us5182d_get_platform_data(struct iio_dev *indio_dev) @@ -399,6 +787,8 @@ static void us5182d_get_platform_data(struct iio_dev *indio_dev) "upisemi,lower-dark-gain", &data->lower_dark_gain)) data->lower_dark_gain = US5182D_REG_AUTO_LDARK_GAIN_DEFAULT; + data->default_continuous = device_property_read_bool(&data->client->dev, + "upisemi,continuous"); } static int us5182d_dark_gain_config(struct iio_dev *indio_dev) @@ -426,6 +816,33 @@ static int us5182d_dark_gain_config(struct iio_dev *indio_dev) US5182D_REG_DARK_AUTO_EN_DEFAULT); } +static irqreturn_t us5182d_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct us5182d_data *data = iio_priv(indio_dev); + enum iio_event_direction dir; + int ret; + u64 ev; + + ret = i2c_smbus_read_byte_data(data->client, US5182D_REG_CFG0); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return IRQ_HANDLED; + } + + dir = ret & US5182D_CFG0_PROX ? IIO_EV_DIR_RISING : IIO_EV_DIR_FALLING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 1, IIO_EV_TYPE_THRESH, dir); + + iio_push_event(indio_dev, ev, iio_get_time_ns()); + + ret = i2c_smbus_write_byte_data(data->client, US5182D_REG_CFG0, + ret & ~US5182D_CFG0_PX_IRQ); + if (ret < 0) + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + + return IRQ_HANDLED; +} + static int us5182d_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -457,6 +874,16 @@ static int us5182d_probe(struct i2c_client *client, return (ret < 0) ? ret : -ENODEV; } + if (client->irq > 0) { + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + us5182d_irq_thread_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "us5182d-irq", indio_dev); + if (ret < 0) + return ret; + } else + dev_warn(&client->dev, "no valid irq found\n"); + us5182d_get_platform_data(indio_dev); ret = us5182d_init(indio_dev); if (ret < 0) @@ -464,18 +891,73 @@ static int us5182d_probe(struct i2c_client *client, ret = us5182d_dark_gain_config(indio_dev); if (ret < 0) - return ret; + goto out_err; + + if (data->default_continuous) { + pm_runtime_set_active(&client->dev); + if (ret < 0) + goto out_err; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, + US5182D_SLEEP_MS); + pm_runtime_use_autosuspend(&client->dev); + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto out_err; + + return 0; + +out_err: + us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + return ret; - return iio_device_register(indio_dev); } static int us5182d_remove(struct i2c_client *client) { + struct us5182d_data *data = iio_priv(i2c_get_clientdata(client)); + iio_device_unregister(i2c_get_clientdata(client)); - return i2c_smbus_write_byte_data(client, US5182D_REG_CFG0, - US5182D_CFG0_SHUTDOWN_EN); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); +} + +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM) +static int us5182d_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, US5182D_CFG0_SHUTDOWN_EN); + + return 0; } +static int us5182d_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct us5182d_data *data = iio_priv(indio_dev); + + if (data->power_mode == US5182D_CONTINUOUS) + return us5182d_shutdown_en(data, + ~US5182D_CFG0_SHUTDOWN_EN & 0xff); + + return 0; +} +#endif + +static const struct dev_pm_ops us5182d_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(us5182d_suspend, us5182d_resume) + SET_RUNTIME_PM_OPS(us5182d_suspend, us5182d_resume, NULL) +}; + static const struct acpi_device_id us5182d_acpi_match[] = { { "USD5182", 0}, {} @@ -493,6 +975,7 @@ MODULE_DEVICE_TABLE(i2c, us5182d_id); static struct i2c_driver us5182d_driver = { .driver = { .name = US5182D_DRV_NAME, + .pm = &us5182d_pm_ops, .acpi_match_table = ACPI_PTR(us5182d_acpi_match), }, .probe = us5182d_probe, diff --git b/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c new file mode 100644 index 0000000..bc1c4cb --- /dev/null +++ b/drivers/iio/light/veml6070.c @@ -0,0 +1,218 @@ +/* + * veml6070.c - Support for Vishay VEML6070 UV A light sensor + * + * Copyright 2016 Peter Meerwald-Stadler + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * IIO driver for VEML6070 (7-bit I2C slave addresses 0x38 and 0x39) + * + * TODO: integration time, ACK signal + */ + +#include +#include +#include +#include +#include + +#include +#include + +#define VEML6070_DRV_NAME "veml6070" + +#define VEML6070_ADDR_CONFIG_DATA_MSB 0x38 /* read: MSB data, write: config */ +#define VEML6070_ADDR_DATA_LSB 0x39 /* LSB data */ + +#define VEML6070_COMMAND_ACK BIT(5) /* raise interrupt when over threshold */ +#define VEML6070_COMMAND_IT GENMASK(3, 2) /* bit mask integration time */ +#define VEML6070_COMMAND_RSRVD BIT(1) /* reserved, set to 1 */ +#define VEML6070_COMMAND_SD BIT(0) /* shutdown mode when set */ + +#define VEML6070_IT_10 0x04 /* integration time 1x */ + +struct veml6070_data { + struct i2c_client *client1; + struct i2c_client *client2; + u8 config; + struct mutex lock; +}; + +static int veml6070_read(struct veml6070_data *data) +{ + int ret; + u8 msb, lsb; + + mutex_lock(&data->lock); + + /* disable shutdown */ + ret = i2c_smbus_write_byte(data->client1, + data->config & ~VEML6070_COMMAND_SD); + if (ret < 0) + goto out; + + msleep(125 + 10); /* measurement takes up to 125 ms for IT 1x */ + + ret = i2c_smbus_read_byte(data->client2); /* read MSB, address 0x39 */ + if (ret < 0) + goto out; + msb = ret; + + ret = i2c_smbus_read_byte(data->client1); /* read LSB, address 0x38 */ + if (ret < 0) + goto out; + lsb = ret; + + /* shutdown again */ + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + goto out; + + ret = (msb << 8) | lsb; + +out: + mutex_unlock(&data->lock); + return ret; +} + +static const struct iio_chan_spec veml6070_channels[] = { + { + .type = IIO_INTENSITY, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_UV, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + }, + { + .type = IIO_UVINDEX, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + } +}; + +static int veml6070_to_uv_index(unsigned val) +{ + /* + * conversion of raw UV intensity values to UV index depends on + * integration time (IT) and value of the resistor connected to + * the RSET pin (default: 270 KOhm) + */ + unsigned uvi[11] = { + 187, 373, 560, /* low */ + 746, 933, 1120, /* moderate */ + 1308, 1494, /* high */ + 1681, 1868, 2054}; /* very high */ + int i; + + for (i = 0; i < ARRAY_SIZE(uvi); i++) + if (val <= uvi[i]) + return i; + + return 11; /* extreme */ +} + +static int veml6070_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct veml6070_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + case IIO_CHAN_INFO_PROCESSED: + ret = veml6070_read(data); + if (ret < 0) + return ret; + if (mask == IIO_CHAN_INFO_PROCESSED) + *val = veml6070_to_uv_index(ret); + else + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info veml6070_info = { + .read_raw = veml6070_read_raw, + .driver_module = THIS_MODULE, +}; + +static int veml6070_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct veml6070_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + data->client1 = client; + mutex_init(&data->lock); + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &veml6070_info; + indio_dev->channels = veml6070_channels; + indio_dev->num_channels = ARRAY_SIZE(veml6070_channels); + indio_dev->name = VEML6070_DRV_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + + data->client2 = i2c_new_dummy(client->adapter, VEML6070_ADDR_DATA_LSB); + if (!data->client2) { + dev_err(&client->dev, "i2c device for second chip address failed\n"); + return -ENODEV; + } + + data->config = VEML6070_IT_10 | VEML6070_COMMAND_RSRVD | + VEML6070_COMMAND_SD; + ret = i2c_smbus_write_byte(data->client1, data->config); + if (ret < 0) + goto fail; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto fail; + + return ret; + +fail: + i2c_unregister_device(data->client2); + return ret; +} + +static int veml6070_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct veml6070_data *data = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + i2c_unregister_device(data->client2); + + return 0; +} + +static const struct i2c_device_id veml6070_id[] = { + { "veml6070", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, veml6070_id); + +static struct i2c_driver veml6070_driver = { + .driver = { + .name = VEML6070_DRV_NAME, + }, + .probe = veml6070_probe, + .remove = veml6070_remove, + .id_table = veml6070_id, +}; + +module_i2c_driver(veml6070_driver); + +MODULE_AUTHOR("Peter Meerwald-Stadler "); +MODULE_DESCRIPTION("Vishay VEML6070 UV A light sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/Kconfig b/drivers/iio/magnetometer/Kconfig index 868abad..84e6559 100644 --- a/drivers/iio/magnetometer/Kconfig +++ b/drivers/iio/magnetometer/Kconfig @@ -9,6 +9,8 @@ config AK8975 tristate "Asahi Kasei AK 3-Axis Magnetometer" depends on I2C depends on GPIOLIB || COMPILE_TEST + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Say yes here to build support for Asahi Kasei AK8975, AK8963, AK09911 or AK09912 3-Axis Magnetometer. @@ -25,22 +27,41 @@ config AK09911 Deprecated: AK09911 is now supported by AK8975 driver. config BMC150_MAGN - tristate "Bosch BMC150 Magnetometer Driver" - depends on I2C - select REGMAP_I2C + tristate select IIO_BUFFER select IIO_TRIGGERED_BUFFER + +config BMC150_MAGN_I2C + tristate "Bosch BMC150 I2C Magnetometer Driver" + depends on I2C + select BMC150_MAGN + select REGMAP_I2C help - Say yes here to build support for the BMC150 magnetometer. + Say yes here to build support for the BMC150 magnetometer with + I2C interface. + + This is a combo module with both accelerometer and magnetometer. + This driver is only implementing magnetometer part, which has + its own address and register map. - Currently this only supports the device via an i2c interface. + To compile this driver as a module, choose M here: the module will be + called bmc150_magn_i2c. + +config BMC150_MAGN_SPI + tristate "Bosch BMC150 SPI Magnetometer Driver" + depends on SPI + select BMC150_MAGN + select REGMAP_SPI + help + Say yes here to build support for the BMC150 magnetometer with + SPI interface. This is a combo module with both accelerometer and magnetometer. This driver is only implementing magnetometer part, which has its own address and register map. To compile this driver as a module, choose M here: the module will be - called bmc150_magn. + called bmc150_magn_spi. config MAG3110 tristate "Freescale MAG3110 3-Axis Magnetometer" @@ -105,4 +126,37 @@ config IIO_ST_MAGN_SPI_3AXIS depends on IIO_ST_MAGN_3AXIS depends on IIO_ST_SENSORS_SPI +config SENSORS_HMC5843 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SENSORS_HMC5843_I2C + tristate "Honeywell HMC5843/5883/5883L 3-Axis Magnetometer (I2C)" + depends on I2C + select SENSORS_HMC5843 + select REGMAP_I2C + help + Say Y here to add support for the Honeywell HMC5843, HMC5883 and + HMC5883L 3-Axis Magnetometer (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_i2c (support for HMC5843, HMC5883, HMC5883L and HMC5983) + +config SENSORS_HMC5843_SPI + tristate "Honeywell HMC5983 3-Axis Magnetometer (SPI)" + depends on SPI_MASTER + select SENSORS_HMC5843 + select REGMAP_SPI + help + Say Y here to add support for the Honeywell HMC5983 3-Axis Magnetometer + (digital compass). + + This driver can also be compiled as a set of modules. + If so, these modules will be created: + - hmc5843_core (core functions) + - hmc5843_spi (support for HMC5983) + endmenu diff --git a/drivers/iio/magnetometer/Makefile b/drivers/iio/magnetometer/Makefile index 2c72df4..92a745c 100644 --- a/drivers/iio/magnetometer/Makefile +++ b/drivers/iio/magnetometer/Makefile @@ -5,6 +5,9 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AK8975) += ak8975.o obj-$(CONFIG_BMC150_MAGN) += bmc150_magn.o +obj-$(CONFIG_BMC150_MAGN_I2C) += bmc150_magn_i2c.o +obj-$(CONFIG_BMC150_MAGN_SPI) += bmc150_magn_spi.o + obj-$(CONFIG_MAG3110) += mag3110.o obj-$(CONFIG_HID_SENSOR_MAGNETOMETER_3D) += hid-sensor-magn-3d.o obj-$(CONFIG_MMC35240) += mmc35240.o @@ -15,3 +18,7 @@ st_magn-$(CONFIG_IIO_BUFFER) += st_magn_buffer.o obj-$(CONFIG_IIO_ST_MAGN_I2C_3AXIS) += st_magn_i2c.o obj-$(CONFIG_IIO_ST_MAGN_SPI_3AXIS) += st_magn_spi.o + +obj-$(CONFIG_SENSORS_HMC5843) += hmc5843_core.o +obj-$(CONFIG_SENSORS_HMC5843_I2C) += hmc5843_i2c.o +obj-$(CONFIG_SENSORS_HMC5843_SPI) += hmc5843_spi.o diff --git a/drivers/iio/magnetometer/ak8975.c b/drivers/iio/magnetometer/ak8975.c index f2a7f72..609a2c4 100644 --- a/drivers/iio/magnetometer/ak8975.c +++ b/drivers/iio/magnetometer/ak8975.c @@ -32,9 +32,17 @@ #include #include #include +#include #include #include +#include +#include +#include +#include + +#include + /* * Register definitions, as well as various shifts and masks to get at the * individual fields of the registers. @@ -252,7 +260,7 @@ struct ak_def { u8 data_regs[3]; }; -static struct ak_def ak_def_array[AK_MAX_TYPE] = { +static const struct ak_def ak_def_array[AK_MAX_TYPE] = { { .type = AK8975, .raw_to_gauss = ak8975_raw_to_gauss, @@ -360,8 +368,7 @@ static struct ak_def ak_def_array[AK_MAX_TYPE] = { */ struct ak8975_data { struct i2c_client *client; - struct ak_def *def; - struct attribute_group attrs; + const struct ak_def *def; struct mutex lock; u8 asa[3]; long raw_to_gauss[3]; @@ -370,8 +377,41 @@ struct ak8975_data { wait_queue_head_t data_ready_queue; unsigned long flags; u8 cntl_cache; + struct iio_mount_matrix orientation; + struct regulator *vdd; }; +/* Enable attached power regulator if any. */ +static int ak8975_power_on(struct i2c_client *client) +{ + const struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct ak8975_data *data = iio_priv(indio_dev); + int ret; + + data->vdd = devm_regulator_get(&client->dev, "vdd"); + if (IS_ERR_OR_NULL(data->vdd)) { + ret = PTR_ERR(data->vdd); + if (ret == -ENODEV) + ret = 0; + } else { + ret = regulator_enable(data->vdd); + } + + if (ret) + dev_err(&client->dev, "failed to enable Vdd supply: %d\n", ret); + return ret; +} + +/* Disable attached power regulator if any. */ +static void ak8975_power_off(const struct i2c_client *client) +{ + const struct iio_dev *indio_dev = i2c_get_clientdata(client); + const struct ak8975_data *data = iio_priv(indio_dev); + + if (!IS_ERR_OR_NULL(data->vdd)) + regulator_disable(data->vdd); +} + /* * Return 0 if the i2c device is the one we expect. * return a negative error number otherwise @@ -601,22 +641,15 @@ static int wait_conversion_complete_interrupt(struct ak8975_data *data) return ret > 0 ? 0 : -ETIME; } -/* - * Emits the raw flux value for the x, y, or z axis. - */ -static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) +static int ak8975_start_read_axis(struct ak8975_data *data, + const struct i2c_client *client) { - struct ak8975_data *data = iio_priv(indio_dev); - struct i2c_client *client = data->client; - int ret; - - mutex_lock(&data->lock); - /* Set up the device for taking a sample. */ - ret = ak8975_set_mode(data, MODE_ONCE); + int ret = ak8975_set_mode(data, MODE_ONCE); + if (ret < 0) { dev_err(&client->dev, "Error in setting operating mode\n"); - goto exit; + return ret; } /* Wait for the conversion to complete. */ @@ -627,7 +660,7 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) else ret = wait_conversion_complete_polled(data); if (ret < 0) - goto exit; + return ret; /* This will be executed only for non-interrupt based waiting case */ if (ret & data->def->ctrl_masks[ST1_DRDY]) { @@ -635,32 +668,45 @@ static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) data->def->ctrl_regs[ST2]); if (ret < 0) { dev_err(&client->dev, "Error in reading ST2\n"); - goto exit; + return ret; } if (ret & (data->def->ctrl_masks[ST2_DERR] | data->def->ctrl_masks[ST2_HOFL])) { dev_err(&client->dev, "ST2 status error 0x%x\n", ret); - ret = -EINVAL; - goto exit; + return -EINVAL; } } - /* Read the flux value from the appropriate register - (the register is specified in the iio device attributes). */ - ret = i2c_smbus_read_word_data(client, data->def->data_regs[index]); - if (ret < 0) { - dev_err(&client->dev, "Read axis data fails\n"); + return 0; +} + +/* Retrieve raw flux value for one of the x, y, or z axis. */ +static int ak8975_read_axis(struct iio_dev *indio_dev, int index, int *val) +{ + struct ak8975_data *data = iio_priv(indio_dev); + const struct i2c_client *client = data->client; + const struct ak_def *def = data->def; + int ret; + + mutex_lock(&data->lock); + + ret = ak8975_start_read_axis(data, client); + if (ret) + goto exit; + + ret = i2c_smbus_read_word_data(client, def->data_regs[index]); + if (ret < 0) goto exit; - } mutex_unlock(&data->lock); /* Clamp to valid range. */ - *val = clamp_t(s16, ret, -data->def->range, data->def->range); + *val = clamp_t(s16, ret, -def->range, def->range); return IIO_VAL_INT; exit: mutex_unlock(&data->lock); + dev_err(&client->dev, "Error in reading axis\n"); return ret; } @@ -682,6 +728,18 @@ static int ak8975_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static const struct iio_mount_matrix * +ak8975_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + return &((struct ak8975_data *)iio_priv(indio_dev))->orientation; +} + +static const struct iio_chan_spec_ext_info ak8975_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_DIR, ak8975_get_mount_matrix), + { }, +}; + #define AK8975_CHANNEL(axis, index) \ { \ .type = IIO_MAGN, \ @@ -690,12 +748,23 @@ static int ak8975_read_raw(struct iio_dev *indio_dev, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ BIT(IIO_CHAN_INFO_SCALE), \ .address = index, \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU \ + }, \ + .ext_info = ak8975_ext_info, \ } static const struct iio_chan_spec ak8975_channels[] = { AK8975_CHANNEL(X, 0), AK8975_CHANNEL(Y, 1), AK8975_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), }; +static const unsigned long ak8975_scan_masks[] = { 0x7, 0 }; + static const struct iio_info ak8975_info = { .read_raw = &ak8975_read_raw, .driver_module = THIS_MODULE, @@ -724,6 +793,56 @@ static const char *ak8975_match_acpi_device(struct device *dev, return dev_name(dev); } +static void ak8975_fill_buffer(struct iio_dev *indio_dev) +{ + struct ak8975_data *data = iio_priv(indio_dev); + const struct i2c_client *client = data->client; + const struct ak_def *def = data->def; + int ret; + s16 buff[8]; /* 3 x 16 bits axis values + 1 aligned 64 bits timestamp */ + + mutex_lock(&data->lock); + + ret = ak8975_start_read_axis(data, client); + if (ret) + goto unlock; + + /* + * For each axis, read the flux value from the appropriate register + * (the register is specified in the iio device attributes). + */ + ret = i2c_smbus_read_i2c_block_data_or_emulated(client, + def->data_regs[0], + 3 * sizeof(buff[0]), + (u8 *)buff); + if (ret < 0) + goto unlock; + + mutex_unlock(&data->lock); + + /* Clamp to valid range. */ + buff[0] = clamp_t(s16, le16_to_cpu(buff[0]), -def->range, def->range); + buff[1] = clamp_t(s16, le16_to_cpu(buff[1]), -def->range, def->range); + buff[2] = clamp_t(s16, le16_to_cpu(buff[2]), -def->range, def->range); + + iio_push_to_buffers_with_timestamp(indio_dev, buff, iio_get_time_ns()); + return; + +unlock: + mutex_unlock(&data->lock); + dev_err(&client->dev, "Error in reading axes block\n"); +} + +static irqreturn_t ak8975_handle_trigger(int irq, void *p) +{ + const struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + + ak8975_fill_buffer(indio_dev); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + static int ak8975_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -733,10 +852,12 @@ static int ak8975_probe(struct i2c_client *client, int err; const char *name = NULL; enum asahi_compass_chipset chipset = AK_MAX_TYPE; + const struct ak8975_platform_data *pdata = + dev_get_platdata(&client->dev); /* Grab and set up the supplied GPIO. */ - if (client->dev.platform_data) - eoc_gpio = *(int *)(client->dev.platform_data); + if (pdata) + eoc_gpio = pdata->eoc_gpio; else if (client->dev.of_node) eoc_gpio = of_get_gpio(client->dev.of_node, 0); else @@ -770,13 +891,24 @@ static int ak8975_probe(struct i2c_client *client, data->eoc_gpio = eoc_gpio; data->eoc_irq = 0; + if (!pdata) { + err = of_iio_read_mount_matrix(&client->dev, + "mount-matrix", + &data->orientation); + if (err) + return err; + } else + data->orientation = pdata->orientation; + /* id will be NULL when enumerated via ACPI */ if (id) { chipset = (enum asahi_compass_chipset)(id->driver_data); name = id->name; - } else if (ACPI_HANDLE(&client->dev)) + } else if (ACPI_HANDLE(&client->dev)) { name = ak8975_match_acpi_device(&client->dev, &chipset); - else + if (!name) + return -ENODEV; + } else return -ENOSYS; if (chipset >= AK_MAX_TYPE) { @@ -786,10 +918,15 @@ static int ak8975_probe(struct i2c_client *client, } data->def = &ak_def_array[chipset]; + + err = ak8975_power_on(client); + if (err) + return err; + err = ak8975_who_i_am(client, data->def->type); if (err < 0) { dev_err(&client->dev, "Unexpected device\n"); - return err; + goto power_off; } dev_dbg(&client->dev, "Asahi compass chip %s\n", name); @@ -797,7 +934,7 @@ static int ak8975_probe(struct i2c_client *client, err = ak8975_setup(client); if (err < 0) { dev_err(&client->dev, "%s initialization fails\n", name); - return err; + goto power_off; } mutex_init(&data->lock); @@ -805,9 +942,41 @@ static int ak8975_probe(struct i2c_client *client, indio_dev->channels = ak8975_channels; indio_dev->num_channels = ARRAY_SIZE(ak8975_channels); indio_dev->info = &ak8975_info; + indio_dev->available_scan_masks = ak8975_scan_masks; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->name = name; - return devm_iio_device_register(&client->dev, indio_dev); + + err = iio_triggered_buffer_setup(indio_dev, NULL, ak8975_handle_trigger, + NULL); + if (err) { + dev_err(&client->dev, "triggered buffer setup failed\n"); + goto power_off; + } + + err = iio_device_register(indio_dev); + if (err) { + dev_err(&client->dev, "device register failed\n"); + goto cleanup_buffer; + } + + return 0; + +cleanup_buffer: + iio_triggered_buffer_cleanup(indio_dev); +power_off: + ak8975_power_off(client); + return err; +} + +static int ak8975_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ak8975_power_off(client); + + return 0; } static const struct i2c_device_id ak8975_id[] = { @@ -841,6 +1010,7 @@ static struct i2c_driver ak8975_driver = { .acpi_match_table = ACPI_PTR(ak_acpi_match), }, .probe = ak8975_probe, + .remove = ak8975_remove, .id_table = ak8975_id, }; module_i2c_driver(ak8975_driver); diff --git a/drivers/iio/magnetometer/bmc150_magn.c b/drivers/iio/magnetometer/bmc150_magn.c index 1615b23..d104fb8 100644 --- a/drivers/iio/magnetometer/bmc150_magn.c +++ b/drivers/iio/magnetometer/bmc150_magn.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,8 @@ #include #include +#include "bmc150_magn.h" + #define BMC150_MAGN_DRV_NAME "bmc150_magn" #define BMC150_MAGN_IRQ_NAME "bmc150_magn_event" @@ -135,7 +136,7 @@ struct bmc150_magn_trim_regs { } __packed; struct bmc150_magn_data { - struct i2c_client *client; + struct device *dev; /* * 1. Protect this structure. * 2. Serialize sequences that power on/off the device and access HW. @@ -147,6 +148,7 @@ struct bmc150_magn_data { struct iio_trigger *dready_trig; bool dready_trigger_on; int max_odr; + int irq; }; static const struct { @@ -216,7 +218,7 @@ static bool bmc150_magn_is_volatile_reg(struct device *dev, unsigned int reg) } } -static const struct regmap_config bmc150_magn_regmap_config = { +const struct regmap_config bmc150_magn_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -226,6 +228,7 @@ static const struct regmap_config bmc150_magn_regmap_config = { .writeable_reg = bmc150_magn_is_writeable_reg, .volatile_reg = bmc150_magn_is_volatile_reg, }; +EXPORT_SYMBOL(bmc150_magn_regmap_config); static int bmc150_magn_set_power_mode(struct bmc150_magn_data *data, enum bmc150_magn_power_modes mode, @@ -264,17 +267,17 @@ static int bmc150_magn_set_power_state(struct bmc150_magn_data *data, bool on) int ret; if (on) { - ret = pm_runtime_get_sync(&data->client->dev); + ret = pm_runtime_get_sync(data->dev); } else { - pm_runtime_mark_last_busy(&data->client->dev); - ret = pm_runtime_put_autosuspend(&data->client->dev); + pm_runtime_mark_last_busy(data->dev); + ret = pm_runtime_put_autosuspend(data->dev); } if (ret < 0) { - dev_err(&data->client->dev, + dev_err(data->dev, "failed to change power state to %d\n", on); if (on) - pm_runtime_put_noidle(&data->client->dev); + pm_runtime_put_noidle(data->dev); return ret; } @@ -351,7 +354,7 @@ static int bmc150_magn_set_max_odr(struct bmc150_magn_data *data, int rep_xy, /* the maximum selectable read-out frequency from datasheet */ max_odr = 1000000 / (145 * rep_xy + 500 * rep_z + 980); if (odr > max_odr) { - dev_err(&data->client->dev, + dev_err(data->dev, "Can't set oversampling with sampling freq %d\n", odr); return -EINVAL; @@ -685,27 +688,27 @@ static int bmc150_magn_init(struct bmc150_magn_data *data) ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, false); if (ret < 0) { - dev_err(&data->client->dev, + dev_err(data->dev, "Failed to bring up device from suspend mode\n"); return ret; } ret = regmap_read(data->regmap, BMC150_MAGN_REG_CHIP_ID, &chip_id); if (ret < 0) { - dev_err(&data->client->dev, "Failed reading chip id\n"); + dev_err(data->dev, "Failed reading chip id\n"); goto err_poweroff; } if (chip_id != BMC150_MAGN_CHIP_ID_VAL) { - dev_err(&data->client->dev, "Invalid chip id 0x%x\n", chip_id); + dev_err(data->dev, "Invalid chip id 0x%x\n", chip_id); ret = -ENODEV; goto err_poweroff; } - dev_dbg(&data->client->dev, "Chip id %x\n", chip_id); + dev_dbg(data->dev, "Chip id %x\n", chip_id); preset = bmc150_magn_presets_table[BMC150_MAGN_DEFAULT_PRESET]; ret = bmc150_magn_set_odr(data, preset.odr); if (ret < 0) { - dev_err(&data->client->dev, "Failed to set ODR to %d\n", + dev_err(data->dev, "Failed to set ODR to %d\n", preset.odr); goto err_poweroff; } @@ -713,7 +716,7 @@ static int bmc150_magn_init(struct bmc150_magn_data *data) ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_XY, BMC150_MAGN_REPXY_TO_REGVAL(preset.rep_xy)); if (ret < 0) { - dev_err(&data->client->dev, "Failed to set REP XY to %d\n", + dev_err(data->dev, "Failed to set REP XY to %d\n", preset.rep_xy); goto err_poweroff; } @@ -721,7 +724,7 @@ static int bmc150_magn_init(struct bmc150_magn_data *data) ret = regmap_write(data->regmap, BMC150_MAGN_REG_REP_Z, BMC150_MAGN_REPZ_TO_REGVAL(preset.rep_z)); if (ret < 0) { - dev_err(&data->client->dev, "Failed to set REP Z to %d\n", + dev_err(data->dev, "Failed to set REP Z to %d\n", preset.rep_z); goto err_poweroff; } @@ -734,7 +737,7 @@ static int bmc150_magn_init(struct bmc150_magn_data *data) ret = bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL, true); if (ret < 0) { - dev_err(&data->client->dev, "Failed to power on device\n"); + dev_err(data->dev, "Failed to power on device\n"); goto err_poweroff; } @@ -843,41 +846,33 @@ static const char *bmc150_magn_match_acpi_device(struct device *dev) return dev_name(dev); } -static int bmc150_magn_probe(struct i2c_client *client, - const struct i2c_device_id *id) +int bmc150_magn_probe(struct device *dev, struct regmap *regmap, + int irq, const char *name) { struct bmc150_magn_data *data; struct iio_dev *indio_dev; - const char *name = NULL; int ret; - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - i2c_set_clientdata(client, indio_dev); - data->client = client; + dev_set_drvdata(dev, indio_dev); + data->regmap = regmap; + data->irq = irq; + data->dev = dev; - if (id) - name = id->name; - else if (ACPI_HANDLE(&client->dev)) - name = bmc150_magn_match_acpi_device(&client->dev); - else - return -ENOSYS; + if (!name && ACPI_HANDLE(dev)) + name = bmc150_magn_match_acpi_device(dev); mutex_init(&data->mutex); - data->regmap = devm_regmap_init_i2c(client, &bmc150_magn_regmap_config); - if (IS_ERR(data->regmap)) { - dev_err(&client->dev, "Failed to allocate register map\n"); - return PTR_ERR(data->regmap); - } ret = bmc150_magn_init(data); if (ret < 0) return ret; - indio_dev->dev.parent = &client->dev; + indio_dev->dev.parent = dev; indio_dev->channels = bmc150_magn_channels; indio_dev->num_channels = ARRAY_SIZE(bmc150_magn_channels); indio_dev->available_scan_masks = bmc150_magn_scan_masks; @@ -885,35 +880,34 @@ static int bmc150_magn_probe(struct i2c_client *client, indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &bmc150_magn_info; - if (client->irq > 0) { - data->dready_trig = devm_iio_trigger_alloc(&client->dev, + if (irq > 0) { + data->dready_trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, indio_dev->id); if (!data->dready_trig) { ret = -ENOMEM; - dev_err(&client->dev, "iio trigger alloc failed\n"); + dev_err(dev, "iio trigger alloc failed\n"); goto err_poweroff; } - data->dready_trig->dev.parent = &client->dev; + data->dready_trig->dev.parent = dev; data->dready_trig->ops = &bmc150_magn_trigger_ops; iio_trigger_set_drvdata(data->dready_trig, indio_dev); ret = iio_trigger_register(data->dready_trig); if (ret) { - dev_err(&client->dev, "iio trigger register failed\n"); + dev_err(dev, "iio trigger register failed\n"); goto err_poweroff; } - ret = request_threaded_irq(client->irq, + ret = request_threaded_irq(irq, iio_trigger_generic_data_rdy_poll, NULL, IRQF_TRIGGER_RISING | IRQF_ONESHOT, BMC150_MAGN_IRQ_NAME, data->dready_trig); if (ret < 0) { - dev_err(&client->dev, "request irq %d failed\n", - client->irq); + dev_err(dev, "request irq %d failed\n", irq); goto err_trigger_unregister; } } @@ -923,37 +917,33 @@ static int bmc150_magn_probe(struct i2c_client *client, bmc150_magn_trigger_handler, &bmc150_magn_buffer_setup_ops); if (ret < 0) { - dev_err(&client->dev, - "iio triggered buffer setup failed\n"); + dev_err(dev, "iio triggered buffer setup failed\n"); goto err_free_irq; } - ret = iio_device_register(indio_dev); - if (ret < 0) { - dev_err(&client->dev, "unable to register iio device\n"); - goto err_buffer_cleanup; - } - - ret = pm_runtime_set_active(&client->dev); + ret = pm_runtime_set_active(dev); if (ret) - goto err_iio_unregister; + goto err_buffer_cleanup; - pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, + pm_runtime_enable(dev); + pm_runtime_set_autosuspend_delay(dev, BMC150_MAGN_AUTO_SUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&client->dev); + pm_runtime_use_autosuspend(dev); - dev_dbg(&indio_dev->dev, "Registered device %s\n", name); + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + dev_dbg(dev, "Registered device %s\n", name); return 0; -err_iio_unregister: - iio_device_unregister(indio_dev); err_buffer_cleanup: iio_triggered_buffer_cleanup(indio_dev); err_free_irq: - if (client->irq > 0) - free_irq(client->irq, data->dready_trig); + if (irq > 0) + free_irq(irq, data->dready_trig); err_trigger_unregister: if (data->dready_trig) iio_trigger_unregister(data->dready_trig); @@ -961,21 +951,23 @@ err_poweroff: bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_SUSPEND, true); return ret; } +EXPORT_SYMBOL(bmc150_magn_probe); -static int bmc150_magn_remove(struct i2c_client *client) +int bmc150_magn_remove(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_magn_data *data = iio_priv(indio_dev); - pm_runtime_disable(&client->dev); - pm_runtime_set_suspended(&client->dev); - pm_runtime_put_noidle(&client->dev); - iio_device_unregister(indio_dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + iio_triggered_buffer_cleanup(indio_dev); - if (client->irq > 0) - free_irq(data->client->irq, data->dready_trig); + if (data->irq > 0) + free_irq(data->irq, data->dready_trig); if (data->dready_trig) iio_trigger_unregister(data->dready_trig); @@ -986,11 +978,12 @@ static int bmc150_magn_remove(struct i2c_client *client) return 0; } +EXPORT_SYMBOL(bmc150_magn_remove); #ifdef CONFIG_PM static int bmc150_magn_runtime_suspend(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_magn_data *data = iio_priv(indio_dev); int ret; @@ -999,7 +992,7 @@ static int bmc150_magn_runtime_suspend(struct device *dev) true); mutex_unlock(&data->mutex); if (ret < 0) { - dev_err(&data->client->dev, "powering off device failed\n"); + dev_err(dev, "powering off device failed\n"); return ret; } return 0; @@ -1010,7 +1003,7 @@ static int bmc150_magn_runtime_suspend(struct device *dev) */ static int bmc150_magn_runtime_resume(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_magn_data *data = iio_priv(indio_dev); return bmc150_magn_set_power_mode(data, BMC150_MAGN_POWER_MODE_NORMAL, @@ -1021,7 +1014,7 @@ static int bmc150_magn_runtime_resume(struct device *dev) #ifdef CONFIG_PM_SLEEP static int bmc150_magn_suspend(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_magn_data *data = iio_priv(indio_dev); int ret; @@ -1035,7 +1028,7 @@ static int bmc150_magn_suspend(struct device *dev) static int bmc150_magn_resume(struct device *dev) { - struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct iio_dev *indio_dev = dev_get_drvdata(dev); struct bmc150_magn_data *data = iio_priv(indio_dev); int ret; @@ -1048,38 +1041,13 @@ static int bmc150_magn_resume(struct device *dev) } #endif -static const struct dev_pm_ops bmc150_magn_pm_ops = { +const struct dev_pm_ops bmc150_magn_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(bmc150_magn_suspend, bmc150_magn_resume) SET_RUNTIME_PM_OPS(bmc150_magn_runtime_suspend, bmc150_magn_runtime_resume, NULL) }; - -static const struct acpi_device_id bmc150_magn_acpi_match[] = { - {"BMC150B", 0}, - {"BMC156B", 0}, - {}, -}; -MODULE_DEVICE_TABLE(acpi, bmc150_magn_acpi_match); - -static const struct i2c_device_id bmc150_magn_id[] = { - {"bmc150_magn", 0}, - {"bmc156_magn", 0}, - {}, -}; -MODULE_DEVICE_TABLE(i2c, bmc150_magn_id); - -static struct i2c_driver bmc150_magn_driver = { - .driver = { - .name = BMC150_MAGN_DRV_NAME, - .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match), - .pm = &bmc150_magn_pm_ops, - }, - .probe = bmc150_magn_probe, - .remove = bmc150_magn_remove, - .id_table = bmc150_magn_id, -}; -module_i2c_driver(bmc150_magn_driver); +EXPORT_SYMBOL(bmc150_magn_pm_ops); MODULE_AUTHOR("Irina Tirdea "); MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("BMC150 magnetometer driver"); +MODULE_DESCRIPTION("BMC150 magnetometer core driver"); diff --git b/drivers/iio/magnetometer/bmc150_magn.h b/drivers/iio/magnetometer/bmc150_magn.h new file mode 100644 index 0000000..9a8e268 --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn.h @@ -0,0 +1,11 @@ +#ifndef _BMC150_MAGN_H_ +#define _BMC150_MAGN_H_ + +extern const struct regmap_config bmc150_magn_regmap_config; +extern const struct dev_pm_ops bmc150_magn_pm_ops; + +int bmc150_magn_probe(struct device *dev, struct regmap *regmap, int irq, + const char *name); +int bmc150_magn_remove(struct device *dev); + +#endif /* _BMC150_MAGN_H_ */ diff --git b/drivers/iio/magnetometer/bmc150_magn_i2c.c b/drivers/iio/magnetometer/bmc150_magn_i2c.c new file mode 100644 index 0000000..eddc7f0 --- /dev/null +++ b/drivers/iio/magnetometer/bmc150_magn_i2c.c @@ -0,0 +1,77 @@ +/* + * 3-axis magnetometer driver supporting following I2C Bosch-Sensortec chips: + * - BMC150 + * - BMC156 + * + * Copyright (c) 2016, 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 +#include +#include +#include +#include +#include + +#include "bmc150_magn.h" + +static int bmc150_magn_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct regmap *regmap; + const char *name = NULL; + + regmap = devm_regmap_init_i2c(client, &bmc150_magn_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to initialize i2c regmap\n"); + return PTR_ERR(regmap); + } + + if (id) + name = id->name; + + return bmc150_magn_probe(&client->dev, regmap, client->irq, name); +} + +static int bmc150_magn_i2c_remove(struct i2c_client *client) +{ + return bmc150_magn_remove(&client->dev); +} + +static const struct acpi_device_id bmc150_magn_acpi_match[] = { + {"BMC150B", 0}, + {"BMC156B", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bmc150_magn_acpi_match); + +static const struct i2c_device_id bmc150_magn_i2c_id[] = { + {"bmc150_magn", 0}, + {"bmc156_magn", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, bmc150_magn_i2c_id); + +static struct i2c_driver bmc150_magn_driver = { + .driver = { + .name = "bmc150_magn_i2c", + .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match), + .pm = &bmc150_magn_pm_ops, + }, + .probe = bmc150_magn_i2c_probe, + .remove = bmc150_magn_i2c_remove, + .id_table = bmc150_magn_i2c_id, +}; +module_i2c_driver(bmc150_magn_driver); + +MODULE_AUTHOR("Daniel Baluta +#include +#include +#include +#include + +#include "bmc150_magn.h" + +static int bmc150_magn_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + const struct spi_device_id *id = spi_get_device_id(spi); + + regmap = devm_regmap_init_spi(spi, &bmc150_magn_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Failed to register spi regmap %d\n", + (int)PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + return bmc150_magn_probe(&spi->dev, regmap, spi->irq, id->name); +} + +static int bmc150_magn_spi_remove(struct spi_device *spi) +{ + bmc150_magn_remove(&spi->dev); + + return 0; +} + +static const struct spi_device_id bmc150_magn_spi_id[] = { + {"bmc150_magn", 0}, + {"bmc156_magn", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, bmc150_magn_spi_id); + +static const struct acpi_device_id bmc150_magn_acpi_match[] = { + {"BMC150B", 0}, + {"BMC156B", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bmc150_magn_acpi_match); + +static struct spi_driver bmc150_magn_spi_driver = { + .probe = bmc150_magn_spi_probe, + .remove = bmc150_magn_spi_remove, + .id_table = bmc150_magn_spi_id, + .driver = { + .acpi_match_table = ACPI_PTR(bmc150_magn_acpi_match), + .name = "bmc150_magn_spi", + }, +}; +module_spi_driver(bmc150_magn_spi_driver); + +MODULE_AUTHOR("Daniel Baluta + * + * 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. + */ + +#ifndef HMC5843_CORE_H +#define HMC5843_CORE_H + +#include +#include + +#define HMC5843_CONFIG_REG_A 0x00 +#define HMC5843_CONFIG_REG_B 0x01 +#define HMC5843_MODE_REG 0x02 +#define HMC5843_DATA_OUT_MSB_REGS 0x03 +#define HMC5843_STATUS_REG 0x09 +#define HMC5843_ID_REG 0x0a +#define HMC5843_ID_END 0x0c + +enum hmc5843_ids { + HMC5843_ID, + HMC5883_ID, + HMC5883L_ID, + HMC5983_ID, +}; + +/** + * struct hcm5843_data - device specific data + * @dev: actual device + * @lock: update and read regmap data + * @regmap: hardware access register maps + * @variant: describe chip variants + * @buffer: 3x 16-bit channels + padding + 64-bit timestamp + */ +struct hmc5843_data { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + const struct hmc5843_chip_info *variant; + __be16 buffer[8]; +}; + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id, const char *name); +int hmc5843_common_remove(struct device *dev); + +int hmc5843_common_suspend(struct device *dev); +int hmc5843_common_resume(struct device *dev); + +#ifdef CONFIG_PM_SLEEP +static SIMPLE_DEV_PM_OPS(hmc5843_pm_ops, + hmc5843_common_suspend, + hmc5843_common_resume); +#define HMC5843_PM_OPS (&hmc5843_pm_ops) +#else +#define HMC5843_PM_OPS NULL +#endif + +#endif /* HMC5843_CORE_H */ diff --git b/drivers/iio/magnetometer/hmc5843_core.c b/drivers/iio/magnetometer/hmc5843_core.c new file mode 100644 index 0000000..77882b4 --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_core.c @@ -0,0 +1,686 @@ +/* + * Device driver for the the HMC5843 multi-chip module designed + * for low field magnetic sensing. + * + * Copyright (C) 2010 Texas Instruments + * + * Author: Shubhrajyoti Datta + * Acknowledgment: Jonathan Cameron for valuable inputs. + * Support for HMC5883 and HMC5883L by Peter Meerwald . + * Split to multiple files by Josef Gajdusek - 2014 + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hmc5843.h" + +/* + * Range gain settings in (+-)Ga + * Beware: HMC5843 and HMC5883 have different recommended sensor field + * ranges; default corresponds to +-1.0 Ga and +-1.3 Ga, respectively + */ +#define HMC5843_RANGE_GAIN_OFFSET 0x05 +#define HMC5843_RANGE_GAIN_DEFAULT 0x01 +#define HMC5843_RANGE_GAIN_MASK 0xe0 + +/* Device status */ +#define HMC5843_DATA_READY 0x01 +#define HMC5843_DATA_OUTPUT_LOCK 0x02 + +/* Mode register configuration */ +#define HMC5843_MODE_CONVERSION_CONTINUOUS 0x00 +#define HMC5843_MODE_CONVERSION_SINGLE 0x01 +#define HMC5843_MODE_IDLE 0x02 +#define HMC5843_MODE_SLEEP 0x03 +#define HMC5843_MODE_MASK 0x03 + +/* + * HMC5843: Minimum data output rate + * HMC5883: Typical data output rate + */ +#define HMC5843_RATE_OFFSET 0x02 +#define HMC5843_RATE_DEFAULT 0x04 +#define HMC5843_RATE_MASK 0x1c + +/* Device measurement configuration */ +#define HMC5843_MEAS_CONF_NORMAL 0x00 +#define HMC5843_MEAS_CONF_POSITIVE_BIAS 0x01 +#define HMC5843_MEAS_CONF_NEGATIVE_BIAS 0x02 +#define HMC5843_MEAS_CONF_MASK 0x03 + +/* + * API for setting the measurement configuration to + * Normal, Positive bias and Negative bias + * + * From the datasheet: + * 0 - Normal measurement configuration (default): In normal measurement + * configuration the device follows normal measurement flow. Pins BP + * and BN are left floating and high impedance. + * + * 1 - Positive bias configuration: In positive bias configuration, a + * positive current is forced across the resistive load on pins BP + * and BN. + * + * 2 - Negative bias configuration. In negative bias configuration, a + * negative current is forced across the resistive load on pins BP + * and BN. + * + * 3 - Only available on HMC5983. Magnetic sensor is disabled. + * Temperature sensor is enabled. + */ + +static const char *const hmc5843_meas_conf_modes[] = {"normal", "positivebias", + "negativebias"}; + +static const char *const hmc5983_meas_conf_modes[] = {"normal", "positivebias", + "negativebias", + "disabled"}; +/* Scaling factors: 10000000/Gain */ +static const int hmc5843_regval_to_nanoscale[] = { + 6173, 7692, 10309, 12821, 18868, 21739, 25641, 35714 +}; + +static const int hmc5883_regval_to_nanoscale[] = { + 7812, 9766, 13021, 16287, 24096, 27701, 32573, 45662 +}; + +static const int hmc5883l_regval_to_nanoscale[] = { + 7299, 9174, 12195, 15152, 22727, 25641, 30303, 43478 +}; + +/* + * From the datasheet: + * Value | HMC5843 | HMC5883/HMC5883L + * | Data output rate (Hz) | Data output rate (Hz) + * 0 | 0.5 | 0.75 + * 1 | 1 | 1.5 + * 2 | 2 | 3 + * 3 | 5 | 7.5 + * 4 | 10 (default) | 15 + * 5 | 20 | 30 + * 6 | 50 | 75 + * 7 | Not used | Not used + */ +static const int hmc5843_regval_to_samp_freq[][2] = { + {0, 500000}, {1, 0}, {2, 0}, {5, 0}, {10, 0}, {20, 0}, {50, 0} +}; + +static const int hmc5883_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0} +}; + +static const int hmc5983_regval_to_samp_freq[][2] = { + {0, 750000}, {1, 500000}, {3, 0}, {7, 500000}, {15, 0}, {30, 0}, + {75, 0}, {220, 0} +}; + +/* Describe chip variants */ +struct hmc5843_chip_info { + const struct iio_chan_spec *channels; + const int (*regval_to_samp_freq)[2]; + const int n_regval_to_samp_freq; + const int *regval_to_nanoscale; + const int n_regval_to_nanoscale; +}; + +/* The lower two bits contain the current conversion mode */ +static s32 hmc5843_set_mode(struct hmc5843_data *data, u8 operating_mode) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_MODE_REG, + HMC5843_MODE_MASK, operating_mode); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_wait_measurement(struct hmc5843_data *data) +{ + int tries = 150; + unsigned int val; + int ret; + + while (tries-- > 0) { + ret = regmap_read(data->regmap, HMC5843_STATUS_REG, &val); + if (ret < 0) + return ret; + if (val & HMC5843_DATA_READY) + break; + msleep(20); + } + + if (tries < 0) { + dev_err(data->dev, "data not ready\n"); + return -EIO; + } + + return 0; +} + +/* Return the measurement value from the specified channel */ +static int hmc5843_read_measurement(struct hmc5843_data *data, + int idx, int *val) +{ + __be16 values[3]; + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + return ret; + } + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + values, sizeof(values)); + mutex_unlock(&data->lock); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(values[idx]), 15); + return IIO_VAL_INT; +} + +static int hmc5843_set_meas_conf(struct hmc5843_data *data, u8 meas_conf) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_MEAS_CONF_MASK, meas_conf); + mutex_unlock(&data->lock); + + return ret; +} + +static +int hmc5843_show_measurement_configuration(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &val); + if (ret) + return ret; + + return val & HMC5843_MEAS_CONF_MASK; +} + +static +int hmc5843_set_measurement_configuration(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int meas_conf) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + + return hmc5843_set_meas_conf(data, meas_conf); +} + +static const struct iio_enum hmc5843_meas_conf_enum = { + .items = hmc5843_meas_conf_modes, + .num_items = ARRAY_SIZE(hmc5843_meas_conf_modes), + .get = hmc5843_show_measurement_configuration, + .set = hmc5843_set_measurement_configuration, +}; + +static const struct iio_chan_spec_ext_info hmc5843_ext_info[] = { + IIO_ENUM("meas_conf", true, &hmc5843_meas_conf_enum), + IIO_ENUM_AVAILABLE("meas_conf", &hmc5843_meas_conf_enum), + { }, +}; + +static const struct iio_enum hmc5983_meas_conf_enum = { + .items = hmc5983_meas_conf_modes, + .num_items = ARRAY_SIZE(hmc5983_meas_conf_modes), + .get = hmc5843_show_measurement_configuration, + .set = hmc5843_set_measurement_configuration, +}; + +static const struct iio_chan_spec_ext_info hmc5983_ext_info[] = { + IIO_ENUM("meas_conf", true, &hmc5983_meas_conf_enum), + IIO_ENUM_AVAILABLE("meas_conf", &hmc5983_meas_conf_enum), + { }, +}; + +static +ssize_t hmc5843_show_samp_freq_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "%d.%d ", data->variant->regval_to_samp_freq[i][0], + data->variant->regval_to_samp_freq[i][1]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(hmc5843_show_samp_freq_avail); + +static int hmc5843_set_samp_freq(struct hmc5843_data *data, u8 rate) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_A, + HMC5843_RATE_MASK, + rate << HMC5843_RATE_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static int hmc5843_get_samp_freq_index(struct hmc5843_data *data, + int val, int val2) +{ + int i; + + for (i = 0; i < data->variant->n_regval_to_samp_freq; i++) + if (val == data->variant->regval_to_samp_freq[i][0] && + val2 == data->variant->regval_to_samp_freq[i][1]) + return i; + + return -EINVAL; +} + +static int hmc5843_set_range_gain(struct hmc5843_data *data, u8 range) +{ + int ret; + + mutex_lock(&data->lock); + ret = regmap_update_bits(data->regmap, HMC5843_CONFIG_REG_B, + HMC5843_RANGE_GAIN_MASK, + range << HMC5843_RANGE_GAIN_OFFSET); + mutex_unlock(&data->lock); + + return ret; +} + +static ssize_t hmc5843_show_scale_avail(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hmc5843_data *data = iio_priv(dev_to_iio_dev(dev)); + + size_t len = 0; + int i; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, + "0.%09d ", data->variant->regval_to_nanoscale[i]); + + /* replace trailing space by newline */ + buf[len - 1] = '\n'; + + return len; +} + +static IIO_DEVICE_ATTR(scale_available, S_IRUGO, + hmc5843_show_scale_avail, NULL, 0); + +static int hmc5843_get_scale_index(struct hmc5843_data *data, int val, int val2) +{ + int i; + + if (val) + return -EINVAL; + + for (i = 0; i < data->variant->n_regval_to_nanoscale; i++) + if (val2 == data->variant->regval_to_nanoscale[i]) + return i; + + return -EINVAL; +} + +static int hmc5843_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + unsigned int rval; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return hmc5843_read_measurement(data, chan->scan_index, val); + case IIO_CHAN_INFO_SCALE: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_B, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RANGE_GAIN_OFFSET; + *val = 0; + *val2 = data->variant->regval_to_nanoscale[rval]; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + ret = regmap_read(data->regmap, HMC5843_CONFIG_REG_A, &rval); + if (ret < 0) + return ret; + rval >>= HMC5843_RATE_OFFSET; + *val = data->variant->regval_to_samp_freq[rval][0]; + *val2 = data->variant->regval_to_samp_freq[rval][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + return -EINVAL; +} + +static int hmc5843_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct hmc5843_data *data = iio_priv(indio_dev); + int rate, range; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + rate = hmc5843_get_samp_freq_index(data, val, val2); + if (rate < 0) + return -EINVAL; + + return hmc5843_set_samp_freq(data, rate); + case IIO_CHAN_INFO_SCALE: + range = hmc5843_get_scale_index(data, val, val2); + if (range < 0) + return -EINVAL; + + return hmc5843_set_range_gain(data, range); + default: + return -EINVAL; + } +} + +static int hmc5843_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static irqreturn_t hmc5843_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct hmc5843_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->lock); + ret = hmc5843_wait_measurement(data); + if (ret < 0) { + mutex_unlock(&data->lock); + goto done; + } + + ret = regmap_bulk_read(data->regmap, HMC5843_DATA_OUT_MSB_REGS, + data->buffer, 3 * sizeof(__be16)); + + mutex_unlock(&data->lock); + if (ret < 0) + goto done; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns()); + +done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +#define HMC5843_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = hmc5843_ext_info, \ + } + +#define HMC5983_CHANNEL(axis, idx) \ + { \ + .type = IIO_MAGN, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_BE, \ + }, \ + .ext_info = hmc5983_ext_info, \ + } + +static const struct iio_chan_spec hmc5843_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Y, 1), + HMC5843_CHANNEL(Z, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +/* Beware: Y and Z are exchanged on HMC5883 and 5983 */ +static const struct iio_chan_spec hmc5883_channels[] = { + HMC5843_CHANNEL(X, 0), + HMC5843_CHANNEL(Z, 1), + HMC5843_CHANNEL(Y, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static const struct iio_chan_spec hmc5983_channels[] = { + HMC5983_CHANNEL(X, 0), + HMC5983_CHANNEL(Z, 1), + HMC5983_CHANNEL(Y, 2), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static struct attribute *hmc5843_attributes[] = { + &iio_dev_attr_scale_available.dev_attr.attr, + &iio_dev_attr_sampling_frequency_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group hmc5843_group = { + .attrs = hmc5843_attributes, +}; + +static const struct hmc5843_chip_info hmc5843_chip_info_tbl[] = { + [HMC5843_ID] = { + .channels = hmc5843_channels, + .regval_to_samp_freq = hmc5843_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5843_regval_to_samp_freq), + .regval_to_nanoscale = hmc5843_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5843_regval_to_nanoscale), + }, + [HMC5883_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883_regval_to_nanoscale), + }, + [HMC5883L_ID] = { + .channels = hmc5883_channels, + .regval_to_samp_freq = hmc5883_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5883_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + }, + [HMC5983_ID] = { + .channels = hmc5983_channels, + .regval_to_samp_freq = hmc5983_regval_to_samp_freq, + .n_regval_to_samp_freq = + ARRAY_SIZE(hmc5983_regval_to_samp_freq), + .regval_to_nanoscale = hmc5883l_regval_to_nanoscale, + .n_regval_to_nanoscale = + ARRAY_SIZE(hmc5883l_regval_to_nanoscale), + } +}; + +static int hmc5843_init(struct hmc5843_data *data) +{ + int ret; + u8 id[3]; + + ret = regmap_bulk_read(data->regmap, HMC5843_ID_REG, + id, ARRAY_SIZE(id)); + if (ret < 0) + return ret; + if (id[0] != 'H' || id[1] != '4' || id[2] != '3') { + dev_err(data->dev, "no HMC5843/5883/5883L/5983 sensor\n"); + return -ENODEV; + } + + ret = hmc5843_set_meas_conf(data, HMC5843_MEAS_CONF_NORMAL); + if (ret < 0) + return ret; + ret = hmc5843_set_samp_freq(data, HMC5843_RATE_DEFAULT); + if (ret < 0) + return ret; + ret = hmc5843_set_range_gain(data, HMC5843_RANGE_GAIN_DEFAULT); + if (ret < 0) + return ret; + return hmc5843_set_mode(data, HMC5843_MODE_CONVERSION_CONTINUOUS); +} + +static const struct iio_info hmc5843_info = { + .attrs = &hmc5843_group, + .read_raw = &hmc5843_read_raw, + .write_raw = &hmc5843_write_raw, + .write_raw_get_fmt = &hmc5843_write_raw_get_fmt, + .driver_module = THIS_MODULE, +}; + +static const unsigned long hmc5843_scan_masks[] = {0x7, 0}; + +int hmc5843_common_suspend(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_SLEEP); +} +EXPORT_SYMBOL(hmc5843_common_suspend); + +int hmc5843_common_resume(struct device *dev) +{ + return hmc5843_set_mode(iio_priv(dev_get_drvdata(dev)), + HMC5843_MODE_CONVERSION_CONTINUOUS); +} +EXPORT_SYMBOL(hmc5843_common_resume); + +int hmc5843_common_probe(struct device *dev, struct regmap *regmap, + enum hmc5843_ids id, const char *name) +{ + struct hmc5843_data *data; + struct iio_dev *indio_dev; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + dev_set_drvdata(dev, indio_dev); + + /* default settings at probe */ + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + data->variant = &hmc5843_chip_info_tbl[id]; + mutex_init(&data->lock); + + indio_dev->dev.parent = dev; + indio_dev->name = name; + indio_dev->info = &hmc5843_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = data->variant->channels; + indio_dev->num_channels = 4; + indio_dev->available_scan_masks = hmc5843_scan_masks; + + ret = hmc5843_init(data); + if (ret < 0) + return ret; + + ret = iio_triggered_buffer_setup(indio_dev, NULL, + hmc5843_trigger_handler, NULL); + if (ret < 0) + goto buffer_setup_err; + + ret = iio_device_register(indio_dev); + if (ret < 0) + goto buffer_cleanup; + + return 0; + +buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +buffer_setup_err: + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); + return ret; +} +EXPORT_SYMBOL(hmc5843_common_probe); + +int hmc5843_common_remove(struct device *dev) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + + /* sleep mode to save power */ + hmc5843_set_mode(iio_priv(indio_dev), HMC5843_MODE_SLEEP); + + return 0; +} +EXPORT_SYMBOL(hmc5843_common_remove); + +MODULE_AUTHOR("Shubhrajyoti Datta "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 core driver"); +MODULE_LICENSE("GPL"); diff --git b/drivers/iio/magnetometer/hmc5843_i2c.c b/drivers/iio/magnetometer/hmc5843_i2c.c new file mode 100644 index 0000000..3de7f44 --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_i2c.c @@ -0,0 +1,103 @@ +/* + * i2c driver for hmc5843/5843/5883/5883l/5983 + * + * Split from hmc5843.c + * Copyright (C) Josef Gajdusek + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_i2c_probe(struct i2c_client *cli, + const struct i2c_device_id *id) +{ + return hmc5843_common_probe(&cli->dev, + devm_regmap_init_i2c(cli, &hmc5843_i2c_regmap_config), + id->driver_data, id->name); +} + +static int hmc5843_i2c_remove(struct i2c_client *client) +{ + return hmc5843_common_remove(&client->dev); +} + +static const struct i2c_device_id hmc5843_id[] = { + { "hmc5843", HMC5843_ID }, + { "hmc5883", HMC5883_ID }, + { "hmc5883l", HMC5883L_ID }, + { "hmc5983", HMC5983_ID }, + { } +}; +MODULE_DEVICE_TABLE(i2c, hmc5843_id); + +static const struct of_device_id hmc5843_of_match[] = { + { .compatible = "honeywell,hmc5843", .data = (void *)HMC5843_ID }, + { .compatible = "honeywell,hmc5883", .data = (void *)HMC5883_ID }, + { .compatible = "honeywell,hmc5883l", .data = (void *)HMC5883L_ID }, + { .compatible = "honeywell,hmc5983", .data = (void *)HMC5983_ID }, + {} +}; +MODULE_DEVICE_TABLE(of, hmc5843_of_match); + +static struct i2c_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = HMC5843_PM_OPS, + .of_match_table = hmc5843_of_match, + }, + .id_table = hmc5843_id, + .probe = hmc5843_i2c_probe, + .remove = hmc5843_i2c_remove, +}; +module_i2c_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5843/5883/5883L/5983 i2c driver"); +MODULE_LICENSE("GPL"); diff --git b/drivers/iio/magnetometer/hmc5843_spi.c b/drivers/iio/magnetometer/hmc5843_spi.c new file mode 100644 index 0000000..535f03a --- /dev/null +++ b/drivers/iio/magnetometer/hmc5843_spi.c @@ -0,0 +1,100 @@ +/* + * SPI driver for hmc5983 + * + * Copyright (C) Josef Gajdusek + * + * 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. + */ + +#include +#include +#include + +#include "hmc5843.h" + +static const struct regmap_range hmc5843_readable_ranges[] = { + regmap_reg_range(0, HMC5843_ID_END), +}; + +static const struct regmap_access_table hmc5843_readable_table = { + .yes_ranges = hmc5843_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_readable_ranges), +}; + +static const struct regmap_range hmc5843_writable_ranges[] = { + regmap_reg_range(0, HMC5843_MODE_REG), +}; + +static const struct regmap_access_table hmc5843_writable_table = { + .yes_ranges = hmc5843_writable_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_writable_ranges), +}; + +static const struct regmap_range hmc5843_volatile_ranges[] = { + regmap_reg_range(HMC5843_DATA_OUT_MSB_REGS, HMC5843_STATUS_REG), +}; + +static const struct regmap_access_table hmc5843_volatile_table = { + .yes_ranges = hmc5843_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(hmc5843_volatile_ranges), +}; + +static const struct regmap_config hmc5843_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .rd_table = &hmc5843_readable_table, + .wr_table = &hmc5843_writable_table, + .volatile_table = &hmc5843_volatile_table, + + /* Autoincrement address pointer */ + .read_flag_mask = 0xc0, + + .cache_type = REGCACHE_RBTREE, +}; + +static int hmc5843_spi_probe(struct spi_device *spi) +{ + int ret; + const struct spi_device_id *id = spi_get_device_id(spi); + + spi->mode = SPI_MODE_3; + spi->max_speed_hz = 8000000; + spi->bits_per_word = 8; + ret = spi_setup(spi); + if (ret) + return ret; + + return hmc5843_common_probe(&spi->dev, + devm_regmap_init_spi(spi, &hmc5843_spi_regmap_config), + id->driver_data, id->name); +} + +static int hmc5843_spi_remove(struct spi_device *spi) +{ + return hmc5843_common_remove(&spi->dev); +} + +static const struct spi_device_id hmc5843_id[] = { + { "hmc5983", HMC5983_ID }, + { } +}; +MODULE_DEVICE_TABLE(spi, hmc5843_id); + +static struct spi_driver hmc5843_driver = { + .driver = { + .name = "hmc5843", + .pm = HMC5843_PM_OPS, + }, + .id_table = hmc5843_id, + .probe = hmc5843_spi_probe, + .remove = hmc5843_spi_remove, +}; + +module_spi_driver(hmc5843_driver); + +MODULE_AUTHOR("Josef Gajdusek "); +MODULE_DESCRIPTION("HMC5983 SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/magnetometer/st_magn_buffer.c b/drivers/iio/magnetometer/st_magn_buffer.c index ecd3bd0..0a9e8fa 100644 --- a/drivers/iio/magnetometer/st_magn_buffer.c +++ b/drivers/iio/magnetometer/st_magn_buffer.c @@ -82,7 +82,7 @@ static const struct iio_buffer_setup_ops st_magn_buffer_setup_ops = { int st_magn_allocate_ring(struct iio_dev *indio_dev) { - return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + return iio_triggered_buffer_setup(indio_dev, NULL, &st_sensors_trigger_handler, &st_magn_buffer_setup_ops); } diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c index b27f014..8250fc3 100644 --- a/drivers/iio/magnetometer/st_magn_core.c +++ b/drivers/iio/magnetometer/st_magn_core.c @@ -175,6 +175,8 @@ #define ST_MAGN_3_BDU_MASK 0x10 #define ST_MAGN_3_DRDY_IRQ_ADDR 0x62 #define ST_MAGN_3_DRDY_INT_MASK 0x01 +#define ST_MAGN_3_IHL_IRQ_ADDR 0x63 +#define ST_MAGN_3_IHL_IRQ_MASK 0x04 #define ST_MAGN_3_FS_AVL_15000_GAIN 1500 #define ST_MAGN_3_MULTIREAD_BIT false #define ST_MAGN_3_OUT_X_L_ADDR 0x68 @@ -480,6 +482,9 @@ static const struct st_sensor_settings st_magn_sensors_settings[] = { .drdy_irq = { .addr = ST_MAGN_3_DRDY_IRQ_ADDR, .mask_int1 = ST_MAGN_3_DRDY_INT_MASK, + .addr_ihl = ST_MAGN_3_IHL_IRQ_ADDR, + .mask_ihl = ST_MAGN_3_IHL_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_MAGN_3_MULTIREAD_BIT, .bootime = 2, @@ -567,6 +572,7 @@ static const struct iio_info magn_info = { static const struct iio_trigger_ops st_magn_trigger_ops = { .owner = THIS_MODULE, .set_trigger_state = ST_MAGN_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, }; #define ST_MAGN_TRIGGER_OPS (&st_magn_trigger_ops) #else diff --git a/drivers/iio/potentiometer/Kconfig b/drivers/iio/potentiometer/Kconfig index fd75db7..6acb238 100644 --- a/drivers/iio/potentiometer/Kconfig +++ b/drivers/iio/potentiometer/Kconfig @@ -5,6 +5,34 @@ menu "Digital potentiometers" +config DS1803 + tristate "Maxim Integrated DS1803 Digital Potentiometer driver" + depends on I2C + help + Say yes here to build support for the Maxim Integrated DS1803 + digital potentiomenter chip. + + To compile this driver as a module, choose M here: the + module will be called ds1803. + +config MCP4131 + tristate "Microchip MCP413X/414X/415X/416X/423X/424X/425X/426X Digital Potentiometer driver" + depends on SPI + help + Say yes here to build support for the Microchip + MCP4131, MCP4132, + MCP4141, MCP4142, + MCP4151, MCP4152, + MCP4161, MCP4162, + MCP4231, MCP4232, + MCP4241, MCP4242, + MCP4251, MCP4252, + MCP4261, MCP4262, + digital potentiomenter chips. + + To compile this driver as a module, choose M here: the + module will be called mcp4131. + config MCP4531 tristate "Microchip MCP45xx/MCP46xx Digital Potentiometer driver" depends on I2C @@ -17,4 +45,16 @@ config MCP4531 To compile this driver as a module, choose M here: the module will be called mcp4531. +config TPL0102 + tristate "Texas Instruments digital potentiometer driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for the Texas Instruments + TPL0102, TPL0402 + digital potentiometer chips. + + To compile this driver as a module, choose M here: the + module will be called tpl0102. + endmenu diff --git a/drivers/iio/potentiometer/Makefile b/drivers/iio/potentiometer/Makefile index 8afe492..6007faa 100644 --- a/drivers/iio/potentiometer/Makefile +++ b/drivers/iio/potentiometer/Makefile @@ -3,4 +3,7 @@ # # When adding new entries keep the list in alphabetical order +obj-$(CONFIG_DS1803) += ds1803.o +obj-$(CONFIG_MCP4131) += mcp4131.o obj-$(CONFIG_MCP4531) += mcp4531.o +obj-$(CONFIG_TPL0102) += tpl0102.o diff --git b/drivers/iio/potentiometer/ds1803.c b/drivers/iio/potentiometer/ds1803.c new file mode 100644 index 0000000..fb9e2a3 --- /dev/null +++ b/drivers/iio/potentiometer/ds1803.c @@ -0,0 +1,173 @@ +/* + * Maxim Integrated DS1803 digital potentiometer driver + * Copyright (c) 2016 Slawomir Stepien + * + * Datasheet: https://datasheets.maximintegrated.com/en/ds/DS1803.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) i2c address + * ds1803 2 256 10, 50, 100 0101xxx + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#define DS1803_MAX_POS 255 +#define DS1803_WRITE(chan) (0xa8 | ((chan) + 1)) + +enum ds1803_type { + DS1803_010, + DS1803_050, + DS1803_100, +}; + +struct ds1803_cfg { + int kohms; +}; + +static const struct ds1803_cfg ds1803_cfg[] = { + [DS1803_010] = { .kohms = 10, }, + [DS1803_050] = { .kohms = 50, }, + [DS1803_100] = { .kohms = 100, }, +}; + +struct ds1803_data { + struct i2c_client *client; + const struct ds1803_cfg *cfg; +}; + +#define DS1803_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec ds1803_channels[] = { + DS1803_CHANNEL(0), + DS1803_CHANNEL(1), +}; + +static int ds1803_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ds1803_data *data = iio_priv(indio_dev); + int pot = chan->channel; + int ret; + u8 result[indio_dev->num_channels]; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = i2c_master_recv(data->client, result, + indio_dev->num_channels); + if (ret < 0) + return ret; + + *val = result[pot]; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = DS1803_MAX_POS; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int ds1803_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ds1803_data *data = iio_priv(indio_dev); + int pot = chan->channel; + + if (val2 != 0) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > DS1803_MAX_POS || val < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return i2c_smbus_write_byte_data(data->client, DS1803_WRITE(pot), val); +} + +static const struct iio_info ds1803_info = { + .read_raw = ds1803_read_raw, + .write_raw = ds1803_write_raw, + .driver_module = THIS_MODULE, +}; + +static int ds1803_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct ds1803_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + i2c_set_clientdata(client, indio_dev); + + data = iio_priv(indio_dev); + data->client = client; + data->cfg = &ds1803_cfg[id->driver_data]; + + indio_dev->dev.parent = dev; + indio_dev->info = &ds1803_info; + indio_dev->channels = ds1803_channels; + indio_dev->num_channels = ARRAY_SIZE(ds1803_channels); + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +#if defined(CONFIG_OF) +static const struct of_device_id ds1803_dt_ids[] = { + { .compatible = "maxim,ds1803-010", .data = &ds1803_cfg[DS1803_010] }, + { .compatible = "maxim,ds1803-050", .data = &ds1803_cfg[DS1803_050] }, + { .compatible = "maxim,ds1803-100", .data = &ds1803_cfg[DS1803_100] }, + {} +}; +MODULE_DEVICE_TABLE(of, ds1803_dt_ids); +#endif /* CONFIG_OF */ + +static const struct i2c_device_id ds1803_id[] = { + { "ds1803-010", DS1803_010 }, + { "ds1803-050", DS1803_050 }, + { "ds1803-100", DS1803_100 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ds1803_id); + +static struct i2c_driver ds1803_driver = { + .driver = { + .name = "ds1803", + .of_match_table = of_match_ptr(ds1803_dt_ids), + }, + .probe = ds1803_probe, + .id_table = ds1803_id, +}; + +module_i2c_driver(ds1803_driver); + +MODULE_AUTHOR("Slawomir Stepien "); +MODULE_DESCRIPTION("DS1803 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/potentiometer/mcp4131.c b/drivers/iio/potentiometer/mcp4131.c new file mode 100644 index 0000000..4e7e2c6 --- /dev/null +++ b/drivers/iio/potentiometer/mcp4131.c @@ -0,0 +1,494 @@ +/* + * Industrial I/O driver for Microchip digital potentiometers + * + * Copyright (c) 2016 Slawomir Stepien + * Based on: Peter Rosin's code from mcp4531.c + * + * Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22060b.pdf + * + * DEVID #Wipers #Positions Resistor Opts (kOhm) + * mcp4131 1 129 5, 10, 50, 100 + * mcp4132 1 129 5, 10, 50, 100 + * mcp4141 1 129 5, 10, 50, 100 + * mcp4142 1 129 5, 10, 50, 100 + * mcp4151 1 257 5, 10, 50, 100 + * mcp4152 1 257 5, 10, 50, 100 + * mcp4161 1 257 5, 10, 50, 100 + * mcp4162 1 257 5, 10, 50, 100 + * mcp4231 2 129 5, 10, 50, 100 + * mcp4232 2 129 5, 10, 50, 100 + * mcp4241 2 129 5, 10, 50, 100 + * mcp4242 2 129 5, 10, 50, 100 + * mcp4251 2 257 5, 10, 50, 100 + * mcp4252 2 257 5, 10, 50, 100 + * mcp4261 2 257 5, 10, 50, 100 + * mcp4262 2 257 5, 10, 50, 100 + * + * 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. + */ + +/* + * TODO: + * 1. Write wiper setting to EEPROM for EEPROM capable models. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MCP4131_WRITE (0x00 << 2) +#define MCP4131_READ (0x03 << 2) + +#define MCP4131_WIPER_SHIFT 4 +#define MCP4131_CMDERR(r) ((r[0]) & 0x02) +#define MCP4131_RAW(r) ((r[0]) == 0xff ? 0x100 : (r[1])) + +struct mcp4131_cfg { + int wipers; + int max_pos; + int kohms; +}; + +enum mcp4131_type { + MCP413x_502 = 0, + MCP413x_103, + MCP413x_503, + MCP413x_104, + MCP414x_502, + MCP414x_103, + MCP414x_503, + MCP414x_104, + MCP415x_502, + MCP415x_103, + MCP415x_503, + MCP415x_104, + MCP416x_502, + MCP416x_103, + MCP416x_503, + MCP416x_104, + MCP423x_502, + MCP423x_103, + MCP423x_503, + MCP423x_104, + MCP424x_502, + MCP424x_103, + MCP424x_503, + MCP424x_104, + MCP425x_502, + MCP425x_103, + MCP425x_503, + MCP425x_104, + MCP426x_502, + MCP426x_103, + MCP426x_503, + MCP426x_104, +}; + +static const struct mcp4131_cfg mcp4131_cfg[] = { + [MCP413x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, + [MCP413x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, + [MCP413x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, + [MCP413x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, + [MCP414x_502] = { .wipers = 1, .max_pos = 128, .kohms = 5, }, + [MCP414x_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, + [MCP414x_503] = { .wipers = 1, .max_pos = 128, .kohms = 50, }, + [MCP414x_104] = { .wipers = 1, .max_pos = 128, .kohms = 100, }, + [MCP415x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, + [MCP415x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, + [MCP415x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, + [MCP415x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, + [MCP416x_502] = { .wipers = 1, .max_pos = 256, .kohms = 5, }, + [MCP416x_103] = { .wipers = 1, .max_pos = 256, .kohms = 10, }, + [MCP416x_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, + [MCP416x_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, + [MCP423x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, + [MCP423x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, + [MCP423x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, + [MCP423x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, + [MCP424x_502] = { .wipers = 2, .max_pos = 128, .kohms = 5, }, + [MCP424x_103] = { .wipers = 2, .max_pos = 128, .kohms = 10, }, + [MCP424x_503] = { .wipers = 2, .max_pos = 128, .kohms = 50, }, + [MCP424x_104] = { .wipers = 2, .max_pos = 128, .kohms = 100, }, + [MCP425x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, + [MCP425x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, + [MCP425x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, + [MCP425x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, + [MCP426x_502] = { .wipers = 2, .max_pos = 256, .kohms = 5, }, + [MCP426x_103] = { .wipers = 2, .max_pos = 256, .kohms = 10, }, + [MCP426x_503] = { .wipers = 2, .max_pos = 256, .kohms = 50, }, + [MCP426x_104] = { .wipers = 2, .max_pos = 256, .kohms = 100, }, +}; + +struct mcp4131_data { + struct spi_device *spi; + const struct mcp4131_cfg *cfg; + struct mutex lock; + u8 buf[2] ____cacheline_aligned; +}; + +#define MCP4131_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec mcp4131_channels[] = { + MCP4131_CHANNEL(0), + MCP4131_CHANNEL(1), +}; + +static int mcp4131_read(struct spi_device *spi, void *buf, size_t len) +{ + struct spi_transfer t = { + .tx_buf = buf, /* We need to send addr, cmd and 12 bits */ + .rx_buf = buf, + .len = len, + }; + struct spi_message m; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + return spi_sync(spi, &m); +} + +static int mcp4131_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int err; + struct mcp4131_data *data = iio_priv(indio_dev); + int address = chan->channel; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&data->lock); + + data->buf[0] = (address << MCP4131_WIPER_SHIFT) | MCP4131_READ; + data->buf[1] = 0; + + err = mcp4131_read(data->spi, data->buf, 2); + if (err) { + mutex_unlock(&data->lock); + return err; + } + + /* Error, bad address/command combination */ + if (!MCP4131_CMDERR(data->buf)) { + mutex_unlock(&data->lock); + return -EIO; + } + + *val = MCP4131_RAW(data->buf); + mutex_unlock(&data->lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->max_pos; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int mcp4131_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int err; + struct mcp4131_data *data = iio_priv(indio_dev); + int address = chan->channel << MCP4131_WIPER_SHIFT; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (val > data->cfg->max_pos || val < 0) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + mutex_lock(&data->lock); + + data->buf[0] = address << MCP4131_WIPER_SHIFT; + data->buf[0] |= MCP4131_WRITE | (val >> 8); + data->buf[1] = val & 0xFF; /* 8 bits here */ + + err = spi_write(data->spi, data->buf, 2); + mutex_unlock(&data->lock); + + return err; +} + +static const struct iio_info mcp4131_info = { + .read_raw = mcp4131_read_raw, + .write_raw = mcp4131_write_raw, + .driver_module = THIS_MODULE, +}; + +static int mcp4131_probe(struct spi_device *spi) +{ + int err; + struct device *dev = &spi->dev; + unsigned long devid = spi_get_device_id(spi)->driver_data; + struct mcp4131_data *data; + struct iio_dev *indio_dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + spi_set_drvdata(spi, indio_dev); + data->spi = spi; + data->cfg = &mcp4131_cfg[devid]; + + mutex_init(&data->lock); + + indio_dev->dev.parent = dev; + indio_dev->info = &mcp4131_info; + indio_dev->channels = mcp4131_channels; + indio_dev->num_channels = data->cfg->wipers; + indio_dev->name = spi_get_device_id(spi)->name; + + err = devm_iio_device_register(dev, indio_dev); + if (err) { + dev_info(&spi->dev, "Unable to register %s\n", indio_dev->name); + return err; + } + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id mcp4131_dt_ids[] = { + { .compatible = "microchip,mcp4131-502", + .data = &mcp4131_cfg[MCP413x_502] }, + { .compatible = "microchip,mcp4131-103", + .data = &mcp4131_cfg[MCP413x_103] }, + { .compatible = "microchip,mcp4131-503", + .data = &mcp4131_cfg[MCP413x_503] }, + { .compatible = "microchip,mcp4131-104", + .data = &mcp4131_cfg[MCP413x_104] }, + { .compatible = "microchip,mcp4132-502", + .data = &mcp4131_cfg[MCP413x_502] }, + { .compatible = "microchip,mcp4132-103", + .data = &mcp4131_cfg[MCP413x_103] }, + { .compatible = "microchip,mcp4132-503", + .data = &mcp4131_cfg[MCP413x_503] }, + { .compatible = "microchip,mcp4132-104", + .data = &mcp4131_cfg[MCP413x_104] }, + { .compatible = "microchip,mcp4141-502", + .data = &mcp4131_cfg[MCP414x_502] }, + { .compatible = "microchip,mcp4141-103", + .data = &mcp4131_cfg[MCP414x_103] }, + { .compatible = "microchip,mcp4141-503", + .data = &mcp4131_cfg[MCP414x_503] }, + { .compatible = "microchip,mcp4141-104", + .data = &mcp4131_cfg[MCP414x_104] }, + { .compatible = "microchip,mcp4142-502", + .data = &mcp4131_cfg[MCP414x_502] }, + { .compatible = "microchip,mcp4142-103", + .data = &mcp4131_cfg[MCP414x_103] }, + { .compatible = "microchip,mcp4142-503", + .data = &mcp4131_cfg[MCP414x_503] }, + { .compatible = "microchip,mcp4142-104", + .data = &mcp4131_cfg[MCP414x_104] }, + { .compatible = "microchip,mcp4151-502", + .data = &mcp4131_cfg[MCP415x_502] }, + { .compatible = "microchip,mcp4151-103", + .data = &mcp4131_cfg[MCP415x_103] }, + { .compatible = "microchip,mcp4151-503", + .data = &mcp4131_cfg[MCP415x_503] }, + { .compatible = "microchip,mcp4151-104", + .data = &mcp4131_cfg[MCP415x_104] }, + { .compatible = "microchip,mcp4152-502", + .data = &mcp4131_cfg[MCP415x_502] }, + { .compatible = "microchip,mcp4152-103", + .data = &mcp4131_cfg[MCP415x_103] }, + { .compatible = "microchip,mcp4152-503", + .data = &mcp4131_cfg[MCP415x_503] }, + { .compatible = "microchip,mcp4152-104", + .data = &mcp4131_cfg[MCP415x_104] }, + { .compatible = "microchip,mcp4161-502", + .data = &mcp4131_cfg[MCP416x_502] }, + { .compatible = "microchip,mcp4161-103", + .data = &mcp4131_cfg[MCP416x_103] }, + { .compatible = "microchip,mcp4161-503", + .data = &mcp4131_cfg[MCP416x_503] }, + { .compatible = "microchip,mcp4161-104", + .data = &mcp4131_cfg[MCP416x_104] }, + { .compatible = "microchip,mcp4162-502", + .data = &mcp4131_cfg[MCP416x_502] }, + { .compatible = "microchip,mcp4162-103", + .data = &mcp4131_cfg[MCP416x_103] }, + { .compatible = "microchip,mcp4162-503", + .data = &mcp4131_cfg[MCP416x_503] }, + { .compatible = "microchip,mcp4162-104", + .data = &mcp4131_cfg[MCP416x_104] }, + { .compatible = "microchip,mcp4231-502", + .data = &mcp4131_cfg[MCP423x_502] }, + { .compatible = "microchip,mcp4231-103", + .data = &mcp4131_cfg[MCP423x_103] }, + { .compatible = "microchip,mcp4231-503", + .data = &mcp4131_cfg[MCP423x_503] }, + { .compatible = "microchip,mcp4231-104", + .data = &mcp4131_cfg[MCP423x_104] }, + { .compatible = "microchip,mcp4232-502", + .data = &mcp4131_cfg[MCP423x_502] }, + { .compatible = "microchip,mcp4232-103", + .data = &mcp4131_cfg[MCP423x_103] }, + { .compatible = "microchip,mcp4232-503", + .data = &mcp4131_cfg[MCP423x_503] }, + { .compatible = "microchip,mcp4232-104", + .data = &mcp4131_cfg[MCP423x_104] }, + { .compatible = "microchip,mcp4241-502", + .data = &mcp4131_cfg[MCP424x_502] }, + { .compatible = "microchip,mcp4241-103", + .data = &mcp4131_cfg[MCP424x_103] }, + { .compatible = "microchip,mcp4241-503", + .data = &mcp4131_cfg[MCP424x_503] }, + { .compatible = "microchip,mcp4241-104", + .data = &mcp4131_cfg[MCP424x_104] }, + { .compatible = "microchip,mcp4242-502", + .data = &mcp4131_cfg[MCP424x_502] }, + { .compatible = "microchip,mcp4242-103", + .data = &mcp4131_cfg[MCP424x_103] }, + { .compatible = "microchip,mcp4242-503", + .data = &mcp4131_cfg[MCP424x_503] }, + { .compatible = "microchip,mcp4242-104", + .data = &mcp4131_cfg[MCP424x_104] }, + { .compatible = "microchip,mcp4251-502", + .data = &mcp4131_cfg[MCP425x_502] }, + { .compatible = "microchip,mcp4251-103", + .data = &mcp4131_cfg[MCP425x_103] }, + { .compatible = "microchip,mcp4251-503", + .data = &mcp4131_cfg[MCP425x_503] }, + { .compatible = "microchip,mcp4251-104", + .data = &mcp4131_cfg[MCP425x_104] }, + { .compatible = "microchip,mcp4252-502", + .data = &mcp4131_cfg[MCP425x_502] }, + { .compatible = "microchip,mcp4252-103", + .data = &mcp4131_cfg[MCP425x_103] }, + { .compatible = "microchip,mcp4252-503", + .data = &mcp4131_cfg[MCP425x_503] }, + { .compatible = "microchip,mcp4252-104", + .data = &mcp4131_cfg[MCP425x_104] }, + { .compatible = "microchip,mcp4261-502", + .data = &mcp4131_cfg[MCP426x_502] }, + { .compatible = "microchip,mcp4261-103", + .data = &mcp4131_cfg[MCP426x_103] }, + { .compatible = "microchip,mcp4261-503", + .data = &mcp4131_cfg[MCP426x_503] }, + { .compatible = "microchip,mcp4261-104", + .data = &mcp4131_cfg[MCP426x_104] }, + { .compatible = "microchip,mcp4262-502", + .data = &mcp4131_cfg[MCP426x_502] }, + { .compatible = "microchip,mcp4262-103", + .data = &mcp4131_cfg[MCP426x_103] }, + { .compatible = "microchip,mcp4262-503", + .data = &mcp4131_cfg[MCP426x_503] }, + { .compatible = "microchip,mcp4262-104", + .data = &mcp4131_cfg[MCP426x_104] }, + {} +}; +MODULE_DEVICE_TABLE(of, mcp4131_dt_ids); +#endif /* CONFIG_OF */ + +static const struct spi_device_id mcp4131_id[] = { + { "mcp4131-502", MCP413x_502 }, + { "mcp4131-103", MCP413x_103 }, + { "mcp4131-503", MCP413x_503 }, + { "mcp4131-104", MCP413x_104 }, + { "mcp4132-502", MCP413x_502 }, + { "mcp4132-103", MCP413x_103 }, + { "mcp4132-503", MCP413x_503 }, + { "mcp4132-104", MCP413x_104 }, + { "mcp4141-502", MCP414x_502 }, + { "mcp4141-103", MCP414x_103 }, + { "mcp4141-503", MCP414x_503 }, + { "mcp4141-104", MCP414x_104 }, + { "mcp4142-502", MCP414x_502 }, + { "mcp4142-103", MCP414x_103 }, + { "mcp4142-503", MCP414x_503 }, + { "mcp4142-104", MCP414x_104 }, + { "mcp4151-502", MCP415x_502 }, + { "mcp4151-103", MCP415x_103 }, + { "mcp4151-503", MCP415x_503 }, + { "mcp4151-104", MCP415x_104 }, + { "mcp4152-502", MCP415x_502 }, + { "mcp4152-103", MCP415x_103 }, + { "mcp4152-503", MCP415x_503 }, + { "mcp4152-104", MCP415x_104 }, + { "mcp4161-502", MCP416x_502 }, + { "mcp4161-103", MCP416x_103 }, + { "mcp4161-503", MCP416x_503 }, + { "mcp4161-104", MCP416x_104 }, + { "mcp4162-502", MCP416x_502 }, + { "mcp4162-103", MCP416x_103 }, + { "mcp4162-503", MCP416x_503 }, + { "mcp4162-104", MCP416x_104 }, + { "mcp4231-502", MCP423x_502 }, + { "mcp4231-103", MCP423x_103 }, + { "mcp4231-503", MCP423x_503 }, + { "mcp4231-104", MCP423x_104 }, + { "mcp4232-502", MCP423x_502 }, + { "mcp4232-103", MCP423x_103 }, + { "mcp4232-503", MCP423x_503 }, + { "mcp4232-104", MCP423x_104 }, + { "mcp4241-502", MCP424x_502 }, + { "mcp4241-103", MCP424x_103 }, + { "mcp4241-503", MCP424x_503 }, + { "mcp4241-104", MCP424x_104 }, + { "mcp4242-502", MCP424x_502 }, + { "mcp4242-103", MCP424x_103 }, + { "mcp4242-503", MCP424x_503 }, + { "mcp4242-104", MCP424x_104 }, + { "mcp4251-502", MCP425x_502 }, + { "mcp4251-103", MCP425x_103 }, + { "mcp4251-503", MCP425x_503 }, + { "mcp4251-104", MCP425x_104 }, + { "mcp4252-502", MCP425x_502 }, + { "mcp4252-103", MCP425x_103 }, + { "mcp4252-503", MCP425x_503 }, + { "mcp4252-104", MCP425x_104 }, + { "mcp4261-502", MCP426x_502 }, + { "mcp4261-103", MCP426x_103 }, + { "mcp4261-503", MCP426x_503 }, + { "mcp4261-104", MCP426x_104 }, + { "mcp4262-502", MCP426x_502 }, + { "mcp4262-103", MCP426x_103 }, + { "mcp4262-503", MCP426x_503 }, + { "mcp4262-104", MCP426x_104 }, + {} +}; +MODULE_DEVICE_TABLE(spi, mcp4131_id); + +static struct spi_driver mcp4131_driver = { + .driver = { + .name = "mcp4131", + .of_match_table = of_match_ptr(mcp4131_dt_ids), + }, + .probe = mcp4131_probe, + .id_table = mcp4131_id, +}; + +module_spi_driver(mcp4131_driver); + +MODULE_AUTHOR("Slawomir Stepien "); +MODULE_DESCRIPTION("MCP4131 digital potentiometer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/potentiometer/mcp4531.c b/drivers/iio/potentiometer/mcp4531.c index a3f6687..3b72e1a 100644 --- a/drivers/iio/potentiometer/mcp4531.c +++ b/drivers/iio/potentiometer/mcp4531.c @@ -79,7 +79,7 @@ static const struct mcp4531_cfg mcp4531_cfg[] = { struct mcp4531_data { struct i2c_client *client; - unsigned long devid; + const struct mcp4531_cfg *cfg; }; #define MCP4531_CHANNEL(ch) { \ @@ -113,8 +113,8 @@ static int mcp4531_read_raw(struct iio_dev *indio_dev, *val = ret; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - *val = 1000 * mcp4531_cfg[data->devid].kohms; - *val2 = mcp4531_cfg[data->devid].max_pos; + *val = 1000 * data->cfg->kohms; + *val2 = data->cfg->max_pos; return IIO_VAL_FRACTIONAL; } @@ -130,7 +130,7 @@ static int mcp4531_write_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: - if (val > mcp4531_cfg[data->devid].max_pos || val < 0) + if (val > data->cfg->max_pos || val < 0) return -EINVAL; break; default: @@ -152,14 +152,13 @@ static int mcp4531_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; - unsigned long devid = id->driver_data; struct mcp4531_data *data; struct iio_dev *indio_dev; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) { dev_err(dev, "SMBUS Word Data not supported\n"); - return -EIO; + return -EOPNOTSUPP; } indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); @@ -168,12 +167,12 @@ static int mcp4531_probe(struct i2c_client *client, data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; - data->devid = devid; + data->cfg = &mcp4531_cfg[id->driver_data]; indio_dev->dev.parent = dev; indio_dev->info = &mcp4531_info; indio_dev->channels = mcp4531_channels; - indio_dev->num_channels = mcp4531_cfg[devid].wipers; + indio_dev->num_channels = data->cfg->wipers; indio_dev->name = client->name; return devm_iio_device_register(dev, indio_dev); diff --git b/drivers/iio/potentiometer/tpl0102.c b/drivers/iio/potentiometer/tpl0102.c new file mode 100644 index 0000000..5c304d4 --- /dev/null +++ b/drivers/iio/potentiometer/tpl0102.c @@ -0,0 +1,166 @@ +/* + * tpl0102.c - Support for Texas Instruments digital potentiometers + * + * Copyright (C) 2016 Matt Ranostay + * + * 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. + * + * TODO: enable/disable hi-z output control + */ + +#include +#include +#include +#include + +struct tpl0102_cfg { + int wipers; + int max_pos; + int kohms; +}; + +enum tpl0102_type { + CAT5140_503, + CAT5140_104, + TPL0102_104, + TPL0401_103, +}; + +static const struct tpl0102_cfg tpl0102_cfg[] = { + /* on-semiconductor parts */ + [CAT5140_503] = { .wipers = 1, .max_pos = 256, .kohms = 50, }, + [CAT5140_104] = { .wipers = 1, .max_pos = 256, .kohms = 100, }, + /* ti parts */ + [TPL0102_104] = { .wipers = 2, .max_pos = 256, .kohms = 100 }, + [TPL0401_103] = { .wipers = 1, .max_pos = 128, .kohms = 10, }, +}; + +struct tpl0102_data { + struct regmap *regmap; + unsigned long devid; +}; + +static const struct regmap_config tpl0102_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +#define TPL0102_CHANNEL(ch) { \ + .type = IIO_RESISTANCE, \ + .indexed = 1, \ + .output = 1, \ + .channel = (ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ +} + +static const struct iio_chan_spec tpl0102_channels[] = { + TPL0102_CHANNEL(0), + TPL0102_CHANNEL(1), +}; + +static int tpl0102_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct tpl0102_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: { + int ret = regmap_read(data->regmap, chan->channel, val); + + return ret ? ret : IIO_VAL_INT; + } + case IIO_CHAN_INFO_SCALE: + *val = 1000 * tpl0102_cfg[data->devid].kohms; + *val2 = tpl0102_cfg[data->devid].max_pos; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; +} + +static int tpl0102_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct tpl0102_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_RAW) + return -EINVAL; + + if (val >= tpl0102_cfg[data->devid].max_pos || val < 0) + return -EINVAL; + + return regmap_write(data->regmap, chan->channel, val); +} + +static const struct iio_info tpl0102_info = { + .read_raw = tpl0102_read_raw, + .write_raw = tpl0102_write_raw, + .driver_module = THIS_MODULE, +}; + +static int tpl0102_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tpl0102_data *data; + struct iio_dev *indio_dev; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + data = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); + + data->devid = id->driver_data; + data->regmap = devm_regmap_init_i2c(client, &tpl0102_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "regmap initialization failed\n"); + return PTR_ERR(data->regmap); + } + + indio_dev->dev.parent = dev; + indio_dev->info = &tpl0102_info; + indio_dev->channels = tpl0102_channels; + indio_dev->num_channels = tpl0102_cfg[data->devid].wipers; + indio_dev->name = client->name; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct i2c_device_id tpl0102_id[] = { + { "cat5140-503", CAT5140_503 }, + { "cat5140-104", CAT5140_104 }, + { "tpl0102-104", TPL0102_104 }, + { "tpl0401-103", TPL0401_103 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tpl0102_id); + +static struct i2c_driver tpl0102_driver = { + .driver = { + .name = "tpl0102", + }, + .probe = tpl0102_probe, + .id_table = tpl0102_id, +}; + +module_i2c_driver(tpl0102_driver); + +MODULE_AUTHOR("Matt Ranostay "); +MODULE_DESCRIPTION("TPL0102 digital potentiometer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index 6f2e7c9..cda9f12 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -6,15 +6,16 @@ menu "Pressure sensors" config BMP280 - tristate "Bosch Sensortec BMP280 pressure sensor driver" + tristate "Bosch Sensortec BMP180 and BMP280 pressure sensor driver" depends on I2C + depends on !(BMP085_I2C=y || BMP085_I2C=m) select REGMAP_I2C help - Say yes here to build support for Bosch Sensortec BMP280 - pressure and temperature sensor. + Say yes here to build support for Bosch Sensortec BMP180 and BMP280 + pressure and temperature sensors. - To compile this driver as a module, choose M here: the module - will be called bmp280. + To compile this driver as a module, choose M here: the module + will be called bmp280. config HID_SENSOR_PRESS depends on HID_SENSOR_HUB @@ -27,18 +28,44 @@ config HID_SENSOR_PRESS Say yes here to build support for the HID SENSOR Pressure driver - To compile this driver as a module, choose M here: the module - will be called hid-sensor-press. + To compile this driver as a module, choose M here: the module + will be called hid-sensor-press. + +config HP03 + tristate "Hope RF HP03 temperature and pressure sensor driver" + depends on I2C + select REGMAP_I2C + help + Say yes here to build support for Hope RF HP03 pressure and + temperature sensor. + + To compile this driver as a module, choose M here: the module + will be called hp03. config MPL115 + tristate + +config MPL115_I2C tristate "Freescale MPL115A2 pressure sensor driver" depends on I2C + select MPL115 help Say yes here to build support for the Freescale MPL115A2 pressure sensor connected via I2C. - To compile this driver as a module, choose M here: the module - will be called mpl115. + To compile this driver as a module, choose M here: the module + will be called mpl115_i2c. + +config MPL115_SPI + tristate "Freescale MPL115A1 pressure sensor driver" + depends on SPI_MASTER + select MPL115 + help + Say yes here to build support for the Freescale MPL115A1 + pressure sensor connected via SPI. + + To compile this driver as a module, choose M here: the module + will be called mpl115_spi. config MPL3115 tristate "Freescale MPL3115A2 pressure sensor driver" @@ -49,11 +76,13 @@ config MPL3115 Say yes here to build support for the Freescale MPL3115A2 pressure sensor / altimeter. - To compile this driver as a module, choose M here: the module - will be called mpl3115. + To compile this driver as a module, choose M here: the module + will be called mpl3115. config MS5611 tristate "Measurement Specialties MS5611 pressure sensor driver" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER help Say Y here to build support for the Measurement Specialties MS5611, MS5607 pressure and temperature sensors. @@ -82,7 +111,7 @@ config MS5611_SPI config MS5637 tristate "Measurement Specialties MS5637 pressure & temperature sensor" depends on I2C - select IIO_MS_SENSORS_I2C + select IIO_MS_SENSORS_I2C help If you say yes here you get support for the Measurement Specialties MS5637 pressure and temperature sensor. @@ -128,7 +157,17 @@ config T5403 Say yes here to build support for the EPCOS T5403 pressure sensor connected via I2C. - To compile this driver as a module, choose M here: the module - will be called t5403. + To compile this driver as a module, choose M here: the module + will be called t5403. + +config HP206C + tristate "HOPERF HP206C precision barometer and altimeter sensor" + depends on I2C + help + Say yes here to build support for the HOPREF HP206C precision + barometer and altimeter sensor. + + This driver can also be built as a module. If so, the module will + be called hp206c. endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index 46571c9..17d6e7a 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -5,7 +5,10 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_BMP280) += bmp280.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o +obj-$(CONFIG_HP03) += hp03.o obj-$(CONFIG_MPL115) += mpl115.o +obj-$(CONFIG_MPL115_I2C) += mpl115_i2c.o +obj-$(CONFIG_MPL115_SPI) += mpl115_spi.o obj-$(CONFIG_MPL3115) += mpl3115.o obj-$(CONFIG_MS5611) += ms5611_core.o obj-$(CONFIG_MS5611_I2C) += ms5611_i2c.o @@ -15,6 +18,7 @@ obj-$(CONFIG_IIO_ST_PRESS) += st_pressure.o st_pressure-y := st_pressure_core.o st_pressure-$(CONFIG_IIO_BUFFER) += st_pressure_buffer.o obj-$(CONFIG_T5403) += t5403.o +obj-$(CONFIG_HP206C) += hp206c.o obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o diff --git a/drivers/iio/pressure/bmp280.c b/drivers/iio/pressure/bmp280.c index a2602d8..724452d 100644 --- a/drivers/iio/pressure/bmp280.c +++ b/drivers/iio/pressure/bmp280.c @@ -1,12 +1,15 @@ /* * Copyright (c) 2014 Intel Corporation * - * Driver for Bosch Sensortec BMP280 digital pressure sensor. + * Driver for Bosch Sensortec BMP180 and BMP280 digital pressure sensor. * * 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. * + * Datasheet: + * https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP180-DS000-121.pdf + * https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMP280-DS001-12.pdf */ #define pr_fmt(fmt) "bmp280: " fmt @@ -15,9 +18,11 @@ #include #include #include +#include #include #include +/* BMP280 specific registers */ #define BMP280_REG_TEMP_XLSB 0xFC #define BMP280_REG_TEMP_LSB 0xFB #define BMP280_REG_TEMP_MSB 0xFA @@ -26,10 +31,7 @@ #define BMP280_REG_PRESS_MSB 0xF7 #define BMP280_REG_CONFIG 0xF5 -#define BMP280_REG_CTRL_MEAS 0xF4 #define BMP280_REG_STATUS 0xF3 -#define BMP280_REG_RESET 0xE0 -#define BMP280_REG_ID 0xD0 #define BMP280_REG_COMP_TEMP_START 0x88 #define BMP280_COMP_TEMP_REG_COUNT 6 @@ -46,25 +48,49 @@ #define BMP280_OSRS_TEMP_MASK (BIT(7) | BIT(6) | BIT(5)) #define BMP280_OSRS_TEMP_SKIP 0 -#define BMP280_OSRS_TEMP_1X BIT(5) -#define BMP280_OSRS_TEMP_2X BIT(6) -#define BMP280_OSRS_TEMP_4X (BIT(6) | BIT(5)) -#define BMP280_OSRS_TEMP_8X BIT(7) -#define BMP280_OSRS_TEMP_16X (BIT(7) | BIT(5)) +#define BMP280_OSRS_TEMP_X(osrs_t) ((osrs_t) << 5) +#define BMP280_OSRS_TEMP_1X BMP280_OSRS_TEMP_X(1) +#define BMP280_OSRS_TEMP_2X BMP280_OSRS_TEMP_X(2) +#define BMP280_OSRS_TEMP_4X BMP280_OSRS_TEMP_X(3) +#define BMP280_OSRS_TEMP_8X BMP280_OSRS_TEMP_X(4) +#define BMP280_OSRS_TEMP_16X BMP280_OSRS_TEMP_X(5) #define BMP280_OSRS_PRESS_MASK (BIT(4) | BIT(3) | BIT(2)) #define BMP280_OSRS_PRESS_SKIP 0 -#define BMP280_OSRS_PRESS_1X BIT(2) -#define BMP280_OSRS_PRESS_2X BIT(3) -#define BMP280_OSRS_PRESS_4X (BIT(3) | BIT(2)) -#define BMP280_OSRS_PRESS_8X BIT(4) -#define BMP280_OSRS_PRESS_16X (BIT(4) | BIT(2)) +#define BMP280_OSRS_PRESS_X(osrs_p) ((osrs_p) << 2) +#define BMP280_OSRS_PRESS_1X BMP280_OSRS_PRESS_X(1) +#define BMP280_OSRS_PRESS_2X BMP280_OSRS_PRESS_X(2) +#define BMP280_OSRS_PRESS_4X BMP280_OSRS_PRESS_X(3) +#define BMP280_OSRS_PRESS_8X BMP280_OSRS_PRESS_X(4) +#define BMP280_OSRS_PRESS_16X BMP280_OSRS_PRESS_X(5) #define BMP280_MODE_MASK (BIT(1) | BIT(0)) #define BMP280_MODE_SLEEP 0 #define BMP280_MODE_FORCED BIT(0) #define BMP280_MODE_NORMAL (BIT(1) | BIT(0)) +/* BMP180 specific registers */ +#define BMP180_REG_OUT_XLSB 0xF8 +#define BMP180_REG_OUT_LSB 0xF7 +#define BMP180_REG_OUT_MSB 0xF6 + +#define BMP180_REG_CALIB_START 0xAA +#define BMP180_REG_CALIB_COUNT 22 + +#define BMP180_MEAS_SCO BIT(5) +#define BMP180_MEAS_TEMP (0x0E | BMP180_MEAS_SCO) +#define BMP180_MEAS_PRESS_X(oss) ((oss) << 6 | 0x14 | BMP180_MEAS_SCO) +#define BMP180_MEAS_PRESS_1X BMP180_MEAS_PRESS_X(0) +#define BMP180_MEAS_PRESS_2X BMP180_MEAS_PRESS_X(1) +#define BMP180_MEAS_PRESS_4X BMP180_MEAS_PRESS_X(2) +#define BMP180_MEAS_PRESS_8X BMP180_MEAS_PRESS_X(3) + +/* BMP180 and BMP280 common registers */ +#define BMP280_REG_CTRL_MEAS 0xF4 +#define BMP280_REG_RESET 0xE0 +#define BMP280_REG_ID 0xD0 + +#define BMP180_CHIP_ID 0x55 #define BMP280_CHIP_ID 0x58 #define BMP280_SOFT_RESET_VAL 0xB6 @@ -72,6 +98,11 @@ struct bmp280_data { struct i2c_client *client; struct mutex lock; struct regmap *regmap; + const struct bmp280_chip_info *chip_info; + + /* log of base 2 of oversampling rate */ + u8 oversampling_press; + u8 oversampling_temp; /* * Carryover value from temperature conversion, used in pressure @@ -80,9 +111,23 @@ struct bmp280_data { s32 t_fine; }; +struct bmp280_chip_info { + const struct regmap_config *regmap_config; + + const int *oversampling_temp_avail; + int num_oversampling_temp_avail; + + const int *oversampling_press_avail; + int num_oversampling_press_avail; + + int (*chip_config)(struct bmp280_data *); + int (*read_temp)(struct bmp280_data *, int *); + int (*read_press)(struct bmp280_data *, int *, int *); +}; + /* * These enums are used for indexing into the array of compensation - * parameters. + * parameters for BMP280. */ enum { T1, T2, T3 }; enum { P1, P2, P3, P4, P5, P6, P7, P8, P9 }; @@ -90,11 +135,13 @@ enum { P1, P2, P3, P4, P5, P6, P7, P8, P9 }; static const struct iio_chan_spec bmp280_channels[] = { { .type = IIO_PRESSURE, - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), }, { .type = IIO_TEMP, - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), }, }; @@ -290,10 +337,25 @@ static int bmp280_read_raw(struct iio_dev *indio_dev, case IIO_CHAN_INFO_PROCESSED: switch (chan->type) { case IIO_PRESSURE: - ret = bmp280_read_press(data, val, val2); + ret = data->chip_info->read_press(data, val, val2); break; case IIO_TEMP: - ret = bmp280_read_temp(data, val); + ret = data->chip_info->read_temp(data, val); + break; + default: + ret = -EINVAL; + break; + } + break; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_PRESSURE: + *val = 1 << data->oversampling_press; + ret = IIO_VAL_INT; + break; + case IIO_TEMP: + *val = 1 << data->oversampling_temp; + ret = IIO_VAL_INT; break; default: ret = -EINVAL; @@ -310,22 +372,135 @@ static int bmp280_read_raw(struct iio_dev *indio_dev, return ret; } +static int bmp280_write_oversampling_ratio_temp(struct bmp280_data *data, + int val) +{ + int i; + const int *avail = data->chip_info->oversampling_temp_avail; + const int n = data->chip_info->num_oversampling_temp_avail; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + data->oversampling_temp = ilog2(val); + + return data->chip_info->chip_config(data); + } + } + return -EINVAL; +} + +static int bmp280_write_oversampling_ratio_press(struct bmp280_data *data, + int val) +{ + int i; + const int *avail = data->chip_info->oversampling_press_avail; + const int n = data->chip_info->num_oversampling_press_avail; + + for (i = 0; i < n; i++) { + if (avail[i] == val) { + data->oversampling_press = ilog2(val); + + return data->chip_info->chip_config(data); + } + } + return -EINVAL; +} + +static int bmp280_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret = 0; + struct bmp280_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + mutex_lock(&data->lock); + switch (chan->type) { + case IIO_PRESSURE: + ret = bmp280_write_oversampling_ratio_press(data, val); + break; + case IIO_TEMP: + ret = bmp280_write_oversampling_ratio_temp(data, val); + break; + default: + ret = -EINVAL; + break; + } + mutex_unlock(&data->lock); + break; + default: + return -EINVAL; + } + + return ret; +} + +static ssize_t bmp280_show_avail(char *buf, const int *vals, const int n) +{ + size_t len = 0; + int i; + + for (i = 0; i < n; i++) + len += scnprintf(buf + len, PAGE_SIZE - len, "%d ", vals[i]); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t bmp280_show_temp_oversampling_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bmp280_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bmp280_show_avail(buf, data->chip_info->oversampling_temp_avail, + data->chip_info->num_oversampling_temp_avail); +} + +static ssize_t bmp280_show_press_oversampling_avail(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bmp280_data *data = iio_priv(dev_to_iio_dev(dev)); + + return bmp280_show_avail(buf, data->chip_info->oversampling_press_avail, + data->chip_info->num_oversampling_press_avail); +} + +static IIO_DEVICE_ATTR(in_temp_oversampling_ratio_available, + S_IRUGO, bmp280_show_temp_oversampling_avail, NULL, 0); + +static IIO_DEVICE_ATTR(in_pressure_oversampling_ratio_available, + S_IRUGO, bmp280_show_press_oversampling_avail, NULL, 0); + +static struct attribute *bmp280_attributes[] = { + &iio_dev_attr_in_temp_oversampling_ratio_available.dev_attr.attr, + &iio_dev_attr_in_pressure_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group bmp280_attrs_group = { + .attrs = bmp280_attributes, +}; + static const struct iio_info bmp280_info = { .driver_module = THIS_MODULE, .read_raw = &bmp280_read_raw, + .write_raw = &bmp280_write_raw, + .attrs = &bmp280_attrs_group, }; -static int bmp280_chip_init(struct bmp280_data *data) +static int bmp280_chip_config(struct bmp280_data *data) { int ret; + u8 osrs = BMP280_OSRS_TEMP_X(data->oversampling_temp + 1) | + BMP280_OSRS_PRESS_X(data->oversampling_press + 1); ret = regmap_update_bits(data->regmap, BMP280_REG_CTRL_MEAS, BMP280_OSRS_TEMP_MASK | BMP280_OSRS_PRESS_MASK | BMP280_MODE_MASK, - BMP280_OSRS_TEMP_2X | - BMP280_OSRS_PRESS_16X | - BMP280_MODE_NORMAL); + osrs | BMP280_MODE_NORMAL); if (ret < 0) { dev_err(&data->client->dev, "failed to write ctrl_meas register\n"); @@ -344,6 +519,317 @@ static int bmp280_chip_init(struct bmp280_data *data) return ret; } +static const int bmp280_oversampling_avail[] = { 1, 2, 4, 8, 16 }; + +static const struct bmp280_chip_info bmp280_chip_info = { + .regmap_config = &bmp280_regmap_config, + + .oversampling_temp_avail = bmp280_oversampling_avail, + .num_oversampling_temp_avail = ARRAY_SIZE(bmp280_oversampling_avail), + + .oversampling_press_avail = bmp280_oversampling_avail, + .num_oversampling_press_avail = ARRAY_SIZE(bmp280_oversampling_avail), + + .chip_config = bmp280_chip_config, + .read_temp = bmp280_read_temp, + .read_press = bmp280_read_press, +}; + +static bool bmp180_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP280_REG_CTRL_MEAS: + case BMP280_REG_RESET: + return true; + default: + return false; + }; +} + +static bool bmp180_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMP180_REG_OUT_XLSB: + case BMP180_REG_OUT_LSB: + case BMP180_REG_OUT_MSB: + case BMP280_REG_CTRL_MEAS: + return true; + default: + return false; + } +} + +static const struct regmap_config bmp180_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BMP180_REG_OUT_XLSB, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = bmp180_is_writeable_reg, + .volatile_reg = bmp180_is_volatile_reg, +}; + +static int bmp180_measure(struct bmp280_data *data, u8 ctrl_meas) +{ + int ret; + const int conversion_time_max[] = { 4500, 7500, 13500, 25500 }; + unsigned int delay_us; + unsigned int ctrl; + + ret = regmap_write(data->regmap, BMP280_REG_CTRL_MEAS, ctrl_meas); + if (ret) + return ret; + + if (ctrl_meas == BMP180_MEAS_TEMP) + delay_us = 4500; + else + delay_us = conversion_time_max[data->oversampling_press]; + + usleep_range(delay_us, delay_us + 1000); + + ret = regmap_read(data->regmap, BMP280_REG_CTRL_MEAS, &ctrl); + if (ret) + return ret; + + /* The value of this bit reset to "0" after conversion is complete */ + if (ctrl & BMP180_MEAS_SCO) + return -EIO; + + return 0; +} + +static int bmp180_read_adc_temp(struct bmp280_data *data, int *val) +{ + int ret; + __be16 tmp = 0; + + ret = bmp180_measure(data, BMP180_MEAS_TEMP); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, (u8 *)&tmp, 2); + if (ret) + return ret; + + *val = be16_to_cpu(tmp); + + return 0; +} + +/* + * These enums are used for indexing into the array of calibration + * coefficients for BMP180. + */ +enum { AC1, AC2, AC3, AC4, AC5, AC6, B1, B2, MB, MC, MD }; + +struct bmp180_calib { + s16 AC1; + s16 AC2; + s16 AC3; + u16 AC4; + u16 AC5; + u16 AC6; + s16 B1; + s16 B2; + s16 MB; + s16 MC; + s16 MD; +}; + +static int bmp180_read_calib(struct bmp280_data *data, + struct bmp180_calib *calib) +{ + int ret; + int i; + __be16 buf[BMP180_REG_CALIB_COUNT / 2]; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_CALIB_START, buf, + sizeof(buf)); + + if (ret < 0) + return ret; + + /* None of the words has the value 0 or 0xFFFF */ + for (i = 0; i < ARRAY_SIZE(buf); i++) { + if (buf[i] == cpu_to_be16(0) || buf[i] == cpu_to_be16(0xffff)) + return -EIO; + } + + calib->AC1 = be16_to_cpu(buf[AC1]); + calib->AC2 = be16_to_cpu(buf[AC2]); + calib->AC3 = be16_to_cpu(buf[AC3]); + calib->AC4 = be16_to_cpu(buf[AC4]); + calib->AC5 = be16_to_cpu(buf[AC5]); + calib->AC6 = be16_to_cpu(buf[AC6]); + calib->B1 = be16_to_cpu(buf[B1]); + calib->B2 = be16_to_cpu(buf[B2]); + calib->MB = be16_to_cpu(buf[MB]); + calib->MC = be16_to_cpu(buf[MC]); + calib->MD = be16_to_cpu(buf[MD]); + + return 0; +} + +/* + * Returns temperature in DegC, resolution is 0.1 DegC. + * t_fine carries fine temperature as global value. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ +static s32 bmp180_compensate_temp(struct bmp280_data *data, s32 adc_temp) +{ + int ret; + s32 x1, x2; + struct bmp180_calib calib; + + ret = bmp180_read_calib(data, &calib); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read calibration coefficients\n"); + return ret; + } + + x1 = ((adc_temp - calib.AC6) * calib.AC5) >> 15; + x2 = (calib.MC << 11) / (x1 + calib.MD); + data->t_fine = x1 + x2; + + return (data->t_fine + 8) >> 4; +} + +static int bmp180_read_temp(struct bmp280_data *data, int *val) +{ + int ret; + s32 adc_temp, comp_temp; + + ret = bmp180_read_adc_temp(data, &adc_temp); + if (ret) + return ret; + + comp_temp = bmp180_compensate_temp(data, adc_temp); + + /* + * val might be NULL if we're called by the read_press routine, + * who only cares about the carry over t_fine value. + */ + if (val) { + *val = comp_temp * 100; + return IIO_VAL_INT; + } + + return 0; +} + +static int bmp180_read_adc_press(struct bmp280_data *data, int *val) +{ + int ret; + __be32 tmp = 0; + u8 oss = data->oversampling_press; + + ret = bmp180_measure(data, BMP180_MEAS_PRESS_X(oss)); + if (ret) + return ret; + + ret = regmap_bulk_read(data->regmap, BMP180_REG_OUT_MSB, (u8 *)&tmp, 3); + if (ret) + return ret; + + *val = (be32_to_cpu(tmp) >> 8) >> (8 - oss); + + return 0; +} + +/* + * Returns pressure in Pa, resolution is 1 Pa. + * + * Taken from datasheet, Section 3.5, "Calculating pressure and temperature". + */ +static u32 bmp180_compensate_press(struct bmp280_data *data, s32 adc_press) +{ + int ret; + s32 x1, x2, x3, p; + s32 b3, b6; + u32 b4, b7; + s32 oss = data->oversampling_press; + struct bmp180_calib calib; + + ret = bmp180_read_calib(data, &calib); + if (ret < 0) { + dev_err(&data->client->dev, + "failed to read calibration coefficients\n"); + return ret; + } + + b6 = data->t_fine - 4000; + x1 = (calib.B2 * (b6 * b6 >> 12)) >> 11; + x2 = calib.AC2 * b6 >> 11; + x3 = x1 + x2; + b3 = ((((s32)calib.AC1 * 4 + x3) << oss) + 2) / 4; + x1 = calib.AC3 * b6 >> 13; + x2 = (calib.B1 * ((b6 * b6) >> 12)) >> 16; + x3 = (x1 + x2 + 2) >> 2; + b4 = calib.AC4 * (u32)(x3 + 32768) >> 15; + b7 = ((u32)adc_press - b3) * (50000 >> oss); + if (b7 < 0x80000000) + p = (b7 * 2) / b4; + else + p = (b7 / b4) * 2; + + x1 = (p >> 8) * (p >> 8); + x1 = (x1 * 3038) >> 16; + x2 = (-7357 * p) >> 16; + + return p + ((x1 + x2 + 3791) >> 4); +} + +static int bmp180_read_press(struct bmp280_data *data, + int *val, int *val2) +{ + int ret; + s32 adc_press; + u32 comp_press; + + /* Read and compensate temperature so we get a reading of t_fine. */ + ret = bmp180_read_temp(data, NULL); + if (ret) + return ret; + + ret = bmp180_read_adc_press(data, &adc_press); + if (ret) + return ret; + + comp_press = bmp180_compensate_press(data, adc_press); + + *val = comp_press; + *val2 = 1000; + + return IIO_VAL_FRACTIONAL; +} + +static int bmp180_chip_config(struct bmp280_data *data) +{ + return 0; +} + +static const int bmp180_oversampling_temp_avail[] = { 1 }; +static const int bmp180_oversampling_press_avail[] = { 1, 2, 4, 8 }; + +static const struct bmp280_chip_info bmp180_chip_info = { + .regmap_config = &bmp180_regmap_config, + + .oversampling_temp_avail = bmp180_oversampling_temp_avail, + .num_oversampling_temp_avail = + ARRAY_SIZE(bmp180_oversampling_temp_avail), + + .oversampling_press_avail = bmp180_oversampling_press_avail, + .num_oversampling_press_avail = + ARRAY_SIZE(bmp180_oversampling_press_avail), + + .chip_config = bmp180_chip_config, + .read_temp = bmp180_read_temp, + .read_press = bmp180_read_press, +}; + static int bmp280_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -367,7 +853,23 @@ static int bmp280_probe(struct i2c_client *client, indio_dev->info = &bmp280_info; indio_dev->modes = INDIO_DIRECT_MODE; - data->regmap = devm_regmap_init_i2c(client, &bmp280_regmap_config); + switch (id->driver_data) { + case BMP180_CHIP_ID: + data->chip_info = &bmp180_chip_info; + data->oversampling_press = ilog2(8); + data->oversampling_temp = ilog2(1); + break; + case BMP280_CHIP_ID: + data->chip_info = &bmp280_chip_info; + data->oversampling_press = ilog2(16); + data->oversampling_temp = ilog2(2); + break; + default: + return -EINVAL; + } + + data->regmap = devm_regmap_init_i2c(client, + data->chip_info->regmap_config); if (IS_ERR(data->regmap)) { dev_err(&client->dev, "failed to allocate register map\n"); return PTR_ERR(data->regmap); @@ -376,13 +878,13 @@ static int bmp280_probe(struct i2c_client *client, ret = regmap_read(data->regmap, BMP280_REG_ID, &chip_id); if (ret < 0) return ret; - if (chip_id != BMP280_CHIP_ID) { - dev_err(&client->dev, "bad chip id. expected %x got %x\n", - BMP280_CHIP_ID, chip_id); + if (chip_id != id->driver_data) { + dev_err(&client->dev, "bad chip id. expected %lx got %x\n", + id->driver_data, chip_id); return -EINVAL; } - ret = bmp280_chip_init(data); + ret = data->chip_info->chip_config(data); if (ret < 0) return ret; @@ -390,13 +892,17 @@ static int bmp280_probe(struct i2c_client *client, } static const struct acpi_device_id bmp280_acpi_match[] = { - {"BMP0280", 0}, + {"BMP0280", BMP280_CHIP_ID }, + {"BMP0180", BMP180_CHIP_ID }, + {"BMP0085", BMP180_CHIP_ID }, { }, }; MODULE_DEVICE_TABLE(acpi, bmp280_acpi_match); static const struct i2c_device_id bmp280_id[] = { - {"bmp280", 0}, + {"bmp280", BMP280_CHIP_ID }, + {"bmp180", BMP180_CHIP_ID }, + {"bmp085", BMP180_CHIP_ID }, { }, }; MODULE_DEVICE_TABLE(i2c, bmp280_id); @@ -412,5 +918,5 @@ static struct i2c_driver bmp280_driver = { module_i2c_driver(bmp280_driver); MODULE_AUTHOR("Vlad Dogaru "); -MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP280 pressure and temperature sensor"); +MODULE_DESCRIPTION("Driver for Bosch Sensortec BMP180/BMP280 pressure and temperature sensor"); MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/pressure/hp03.c b/drivers/iio/pressure/hp03.c new file mode 100644 index 0000000..ac76515 --- /dev/null +++ b/drivers/iio/pressure/hp03.c @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2016 Marek Vasut + * + * Driver for Hope RF HP03 digital temperature and pressure sensor. + * + * 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. + */ + +#define pr_fmt(fmt) "hp03: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* + * The HP03 sensor occupies two fixed I2C addresses: + * 0x50 ... read-only EEPROM with calibration data + * 0x77 ... read-write ADC for pressure and temperature + */ +#define HP03_EEPROM_ADDR 0x50 +#define HP03_ADC_ADDR 0x77 + +#define HP03_EEPROM_CX_OFFSET 0x10 +#define HP03_EEPROM_AB_OFFSET 0x1e +#define HP03_EEPROM_CD_OFFSET 0x20 + +#define HP03_ADC_WRITE_REG 0xff +#define HP03_ADC_READ_REG 0xfd +#define HP03_ADC_READ_PRESSURE 0xf0 /* D1 in datasheet */ +#define HP03_ADC_READ_TEMP 0xe8 /* D2 in datasheet */ + +struct hp03_priv { + struct i2c_client *client; + struct mutex lock; + struct gpio_desc *xclr_gpio; + + struct i2c_client *eeprom_client; + struct regmap *eeprom_regmap; + + s32 pressure; /* kPa */ + s32 temp; /* Deg. C */ +}; + +static const struct iio_chan_spec hp03_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static bool hp03_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool hp03_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static const struct regmap_config hp03_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = HP03_EEPROM_CD_OFFSET + 1, + .cache_type = REGCACHE_RBTREE, + + .writeable_reg = hp03_is_writeable_reg, + .volatile_reg = hp03_is_volatile_reg, +}; + +static int hp03_get_temp_pressure(struct hp03_priv *priv, const u8 reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(priv->client, HP03_ADC_WRITE_REG, reg); + if (ret < 0) + return ret; + + msleep(50); /* Wait for conversion to finish */ + + return i2c_smbus_read_word_data(priv->client, HP03_ADC_READ_REG); +} + +static int hp03_update_temp_pressure(struct hp03_priv *priv) +{ + struct device *dev = &priv->client->dev; + u8 coefs[18]; + u16 cx_val[7]; + int ab_val, d1_val, d2_val, diff_val, dut, off, sens, x; + int i, ret; + + /* Sample coefficients from EEPROM */ + ret = regmap_bulk_read(priv->eeprom_regmap, HP03_EEPROM_CX_OFFSET, + coefs, sizeof(coefs)); + if (ret < 0) { + dev_err(dev, "Failed to read EEPROM (reg=%02x)\n", + HP03_EEPROM_CX_OFFSET); + return ret; + } + + /* Sample Temperature and Pressure */ + gpiod_set_value_cansleep(priv->xclr_gpio, 1); + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_PRESSURE); + if (ret < 0) { + dev_err(dev, "Failed to read pressure\n"); + goto err_adc; + } + d1_val = ret; + + ret = hp03_get_temp_pressure(priv, HP03_ADC_READ_TEMP); + if (ret < 0) { + dev_err(dev, "Failed to read temperature\n"); + goto err_adc; + } + d2_val = ret; + + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + + /* The Cx coefficients and Temp/Pressure values are MSB first. */ + for (i = 0; i < 7; i++) + cx_val[i] = (coefs[2 * i] << 8) | (coefs[(2 * i) + 1] << 0); + d1_val = ((d1_val >> 8) & 0xff) | ((d1_val & 0xff) << 8); + d2_val = ((d2_val >> 8) & 0xff) | ((d2_val & 0xff) << 8); + + /* Coefficient voodoo from the HP03 datasheet. */ + if (d2_val >= cx_val[4]) + ab_val = coefs[14]; /* A-value */ + else + ab_val = coefs[15]; /* B-value */ + + diff_val = d2_val - cx_val[4]; + dut = (ab_val * (diff_val >> 7) * (diff_val >> 7)) >> coefs[16]; + dut = diff_val - dut; + + off = (cx_val[1] + (((cx_val[3] - 1024) * dut) >> 14)) * 4; + sens = cx_val[0] + ((cx_val[2] * dut) >> 10); + x = ((sens * (d1_val - 7168)) >> 14) - off; + + priv->pressure = ((x * 100) >> 5) + (cx_val[6] * 10); + priv->temp = 250 + ((dut * cx_val[5]) >> 16) - (dut >> coefs[17]); + + return 0; + +err_adc: + gpiod_set_value_cansleep(priv->xclr_gpio, 0); + return ret; +} + +static int hp03_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hp03_priv *priv = iio_priv(indio_dev); + int ret; + + mutex_lock(&priv->lock); + ret = hp03_update_temp_pressure(priv); + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = priv->pressure; + return IIO_VAL_INT; + case IIO_TEMP: + *val = priv->temp; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct iio_info hp03_info = { + .driver_module = THIS_MODULE, + .read_raw = &hp03_read_raw, +}; + +static int hp03_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct hp03_priv *priv; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*priv)); + if (!indio_dev) + return -ENOMEM; + + priv = iio_priv(indio_dev); + priv->client = client; + mutex_init(&priv->lock); + + indio_dev->dev.parent = dev; + indio_dev->name = id->name; + indio_dev->channels = hp03_channels; + indio_dev->num_channels = ARRAY_SIZE(hp03_channels); + indio_dev->info = &hp03_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + priv->xclr_gpio = devm_gpiod_get_index(dev, "xclr", 0, GPIOD_OUT_HIGH); + if (IS_ERR(priv->xclr_gpio)) { + dev_err(dev, "Failed to claim XCLR GPIO\n"); + ret = PTR_ERR(priv->xclr_gpio); + return ret; + } + + /* + * Allocate another device for the on-sensor EEPROM, + * which has it's dedicated I2C address and contains + * the calibration constants for the sensor. + */ + priv->eeprom_client = i2c_new_dummy(client->adapter, HP03_EEPROM_ADDR); + if (!priv->eeprom_client) { + dev_err(dev, "New EEPROM I2C device failed\n"); + return -ENODEV; + } + + priv->eeprom_regmap = regmap_init_i2c(priv->eeprom_client, + &hp03_regmap_config); + if (IS_ERR(priv->eeprom_regmap)) { + dev_err(dev, "Failed to allocate EEPROM regmap\n"); + ret = PTR_ERR(priv->eeprom_regmap); + goto err_cleanup_eeprom_client; + } + + ret = iio_device_register(indio_dev); + if (ret) { + dev_err(dev, "Failed to register IIO device\n"); + goto err_cleanup_eeprom_regmap; + } + + i2c_set_clientdata(client, indio_dev); + + return 0; + +err_cleanup_eeprom_regmap: + regmap_exit(priv->eeprom_regmap); + +err_cleanup_eeprom_client: + i2c_unregister_device(priv->eeprom_client); + return ret; +} + +static int hp03_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct hp03_priv *priv = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + regmap_exit(priv->eeprom_regmap); + i2c_unregister_device(priv->eeprom_client); + + return 0; +} + +static const struct i2c_device_id hp03_id[] = { + { "hp03", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, hp03_id); + +static struct i2c_driver hp03_driver = { + .driver = { + .name = "hp03", + }, + .probe = hp03_probe, + .remove = hp03_remove, + .id_table = hp03_id, +}; +module_i2c_driver(hp03_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Driver for Hope RF HP03 pressure and temperature sensor"); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/iio/pressure/hp206c.c b/drivers/iio/pressure/hp206c.c new file mode 100644 index 0000000..90f2b6e --- /dev/null +++ b/drivers/iio/pressure/hp206c.c @@ -0,0 +1,426 @@ +/* + * hp206c.c - HOPERF HP206C precision barometer and altimeter sensor + * + * Copyright (c) 2016, Intel Corporation. + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * (7-bit I2C slave address 0x76) + * + * Datasheet: + * http://www.hoperf.com/upload/sensor/HP206C_DataSheet_EN_V2.0.pdf + */ + +#include +#include +#include +#include +#include +#include +#include + +/* I2C commands: */ +#define HP206C_CMD_SOFT_RST 0x06 + +#define HP206C_CMD_ADC_CVT 0x40 + +#define HP206C_CMD_ADC_CVT_OSR_4096 0x00 +#define HP206C_CMD_ADC_CVT_OSR_2048 0x04 +#define HP206C_CMD_ADC_CVT_OSR_1024 0x08 +#define HP206C_CMD_ADC_CVT_OSR_512 0x0c +#define HP206C_CMD_ADC_CVT_OSR_256 0x10 +#define HP206C_CMD_ADC_CVT_OSR_128 0x14 + +#define HP206C_CMD_ADC_CVT_CHNL_PT 0x00 +#define HP206C_CMD_ADC_CVT_CHNL_T 0x02 + +#define HP206C_CMD_READ_P 0x30 +#define HP206C_CMD_READ_T 0x32 + +#define HP206C_CMD_READ_REG 0x80 +#define HP206C_CMD_WRITE_REG 0xc0 + +#define HP206C_REG_INT_EN 0x0b +#define HP206C_REG_INT_CFG 0x0c + +#define HP206C_REG_INT_SRC 0x0d +#define HP206C_FLAG_DEV_RDY 0x40 + +#define HP206C_REG_PARA 0x0f +#define HP206C_FLAG_CMPS_EN 0x80 + +/* Maximum spin for DEV_RDY */ +#define HP206C_MAX_DEV_RDY_WAIT_COUNT 20 +#define HP206C_DEV_RDY_WAIT_US 20000 + +struct hp206c_data { + struct mutex mutex; + struct i2c_client *client; + int temp_osr_index; + int pres_osr_index; +}; + +struct hp206c_osr_setting { + u8 osr_mask; + unsigned int temp_conv_time_us; + unsigned int pres_conv_time_us; +}; + +/* Data from Table 5 in datasheet. */ +static const struct hp206c_osr_setting hp206c_osr_settings[] = { + { HP206C_CMD_ADC_CVT_OSR_4096, 65600, 131100 }, + { HP206C_CMD_ADC_CVT_OSR_2048, 32800, 65600 }, + { HP206C_CMD_ADC_CVT_OSR_1024, 16400, 32800 }, + { HP206C_CMD_ADC_CVT_OSR_512, 8200, 16400 }, + { HP206C_CMD_ADC_CVT_OSR_256, 4100, 8200 }, + { HP206C_CMD_ADC_CVT_OSR_128, 2100, 4100 }, +}; +static const int hp206c_osr_rates[] = { 4096, 2048, 1024, 512, 256, 128 }; +static const char hp206c_osr_rates_str[] = "4096 2048 1024 512 256 128"; + +static inline int hp206c_read_reg(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, HP206C_CMD_READ_REG | reg); +} + +static inline int hp206c_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(client, + HP206C_CMD_WRITE_REG | reg, val); +} + +static int hp206c_read_20bit(struct i2c_client *client, u8 cmd) +{ + int ret; + u8 values[3]; + + ret = i2c_smbus_read_i2c_block_data(client, cmd, 3, values); + if (ret < 0) + return ret; + if (ret != 3) + return -EIO; + return ((values[0] & 0xF) << 16) | (values[1] << 8) | (values[2]); +} + +/* Spin for max 160ms until DEV_RDY is 1, or return error. */ +static int hp206c_wait_dev_rdy(struct iio_dev *indio_dev) +{ + int ret; + int count = 0; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + while (++count <= HP206C_MAX_DEV_RDY_WAIT_COUNT) { + ret = hp206c_read_reg(client, HP206C_REG_INT_SRC); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed READ_REG INT_SRC: %d\n", ret); + return ret; + } + if (ret & HP206C_FLAG_DEV_RDY) + return 0; + usleep_range(HP206C_DEV_RDY_WAIT_US, HP206C_DEV_RDY_WAIT_US * 3 / 2); + } + return -ETIMEDOUT; +} + +static int hp206c_set_compensation(struct i2c_client *client, bool enabled) +{ + int val; + + val = hp206c_read_reg(client, HP206C_REG_PARA); + if (val < 0) + return val; + if (enabled) + val |= HP206C_FLAG_CMPS_EN; + else + val &= ~HP206C_FLAG_CMPS_EN; + + return hp206c_write_reg(client, HP206C_REG_PARA, val); +} + +/* Do a soft reset */ +static int hp206c_soft_reset(struct iio_dev *indio_dev) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = i2c_smbus_write_byte(client, HP206C_CMD_SOFT_RST); + if (ret) { + dev_err(&client->dev, "Failed to reset device: %d\n", ret); + return ret; + } + + usleep_range(400, 600); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret) { + dev_err(&client->dev, "Device not ready after soft reset: %d\n", ret); + return ret; + } + + ret = hp206c_set_compensation(client, true); + if (ret) + dev_err(&client->dev, "Failed to enable compensation: %d\n", ret); + return ret; +} + +static int hp206c_conv_and_read(struct iio_dev *indio_dev, + u8 conv_cmd, u8 read_cmd, + unsigned int sleep_us) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + struct i2c_client *client = data->client; + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = i2c_smbus_write_byte(client, conv_cmd); + if (ret < 0) { + dev_err(&indio_dev->dev, "Failed convert: %d\n", ret); + return ret; + } + + usleep_range(sleep_us, sleep_us * 3 / 2); + + ret = hp206c_wait_dev_rdy(indio_dev); + if (ret < 0) { + dev_err(&indio_dev->dev, "Device not ready: %d\n", ret); + return ret; + } + + ret = hp206c_read_20bit(client, read_cmd); + if (ret < 0) + dev_err(&indio_dev->dev, "Failed read: %d\n", ret); + + return ret; +} + +static int hp206c_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct hp206c_data *data = iio_priv(indio_dev); + const struct hp206c_osr_setting *osr_setting; + u8 conv_cmd; + + mutex_lock(&data->mutex); + + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + switch (chan->type) { + case IIO_TEMP: + *val = hp206c_osr_rates[data->temp_osr_index]; + ret = IIO_VAL_INT; + break; + + case IIO_PRESSURE: + *val = hp206c_osr_rates[data->pres_osr_index]; + ret = IIO_VAL_INT; + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_TEMP: + osr_setting = &hp206c_osr_settings[data->temp_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_T; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_T, + osr_setting->temp_conv_time_us); + if (ret >= 0) { + /* 20 significant bits are provided. + * Extend sign over the rest. + */ + *val = sign_extend32(ret, 19); + ret = IIO_VAL_INT; + } + break; + + case IIO_PRESSURE: + osr_setting = &hp206c_osr_settings[data->pres_osr_index]; + conv_cmd = HP206C_CMD_ADC_CVT | + osr_setting->osr_mask | + HP206C_CMD_ADC_CVT_CHNL_PT; + ret = hp206c_conv_and_read(indio_dev, + conv_cmd, + HP206C_CMD_READ_P, + osr_setting->pres_conv_time_us); + if (ret >= 0) { + *val = ret; + ret = IIO_VAL_INT; + } + break; + default: + ret = -EINVAL; + } + break; + + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 0; + *val2 = 10000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + } + break; + + default: + ret = -EINVAL; + } + + mutex_unlock(&data->mutex); + return ret; +} + +static int hp206c_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int ret = 0; + struct hp206c_data *data = iio_priv(indio_dev); + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + mutex_lock(&data->mutex); + switch (chan->type) { + case IIO_TEMP: + data->temp_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + case IIO_PRESSURE: + data->pres_osr_index = find_closest_descending(val, + hp206c_osr_rates, ARRAY_SIZE(hp206c_osr_rates)); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&data->mutex); + return ret; +} + +static const struct iio_chan_spec hp206c_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + }, + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + } +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL(hp206c_osr_rates_str); + +static struct attribute *hp206c_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group hp206c_attribute_group = { + .attrs = hp206c_attributes, +}; + +static const struct iio_info hp206c_info = { + .attrs = &hp206c_attribute_group, + .read_raw = hp206c_read_raw, + .write_raw = hp206c_write_raw, + .driver_module = THIS_MODULE, +}; + +static int hp206c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct iio_dev *indio_dev; + struct hp206c_data *data; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err(&client->dev, "Adapter does not support " + "all required i2c functionality\n"); + return -ENODEV; + } + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + mutex_init(&data->mutex); + + indio_dev->info = &hp206c_info; + indio_dev->name = id->name; + indio_dev->dev.parent = &client->dev; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = hp206c_channels; + indio_dev->num_channels = ARRAY_SIZE(hp206c_channels); + + i2c_set_clientdata(client, indio_dev); + + /* Do a soft reset on probe */ + ret = hp206c_soft_reset(indio_dev); + if (ret) { + dev_err(&client->dev, "Failed to reset on startup: %d\n", ret); + return -ENODEV; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +static const struct i2c_device_id hp206c_id[] = { + {"hp206c"}, + {} +}; + +#ifdef CONFIG_ACPI +static const struct acpi_device_id hp206c_acpi_match[] = { + {"HOP206C", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, hp206c_acpi_match); +#endif + +static struct i2c_driver hp206c_driver = { + .probe = hp206c_probe, + .id_table = hp206c_id, + .driver = { + .name = "hp206c", + .acpi_match_table = ACPI_PTR(hp206c_acpi_match), + }, +}; + +module_i2c_driver(hp206c_driver); + +MODULE_DESCRIPTION("HOPERF HP206C precision barometer and altimeter sensor"); +MODULE_AUTHOR("Leonard Crestez "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/mpl115.c b/drivers/iio/pressure/mpl115.c index a0d7dee..73f2f0c 100644 --- a/drivers/iio/pressure/mpl115.c +++ b/drivers/iio/pressure/mpl115.c @@ -1,5 +1,5 @@ /* - * mpl115.c - Support for Freescale MPL115A2 pressure/temperature sensor + * mpl115.c - Support for Freescale MPL115A pressure/temperature sensor * * Copyright (c) 2014 Peter Meerwald * @@ -7,17 +7,16 @@ * the GNU General Public License. See the file COPYING in the main * directory of this archive for more details. * - * (7-bit I2C slave address 0x60) - * * TODO: shutdown pin * */ #include -#include #include #include +#include "mpl115.h" + #define MPL115_PADC 0x00 /* pressure ADC output value, MSB first, 10 bit */ #define MPL115_TADC 0x02 /* temperature ADC output value, MSB first, 10 bit */ #define MPL115_A0 0x04 /* 12 bit integer, 3 bit fraction */ @@ -27,16 +26,18 @@ #define MPL115_CONVERT 0x12 /* convert temperature and pressure */ struct mpl115_data { - struct i2c_client *client; + struct device *dev; struct mutex lock; s16 a0; s16 b1, b2; s16 c12; + const struct mpl115_ops *ops; }; static int mpl115_request(struct mpl115_data *data) { - int ret = i2c_smbus_write_byte_data(data->client, MPL115_CONVERT, 0); + int ret = data->ops->write(data->dev, MPL115_CONVERT, 0); + if (ret < 0) return ret; @@ -57,12 +58,12 @@ static int mpl115_comp_pressure(struct mpl115_data *data, int *val, int *val2) if (ret < 0) goto done; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_PADC); + ret = data->ops->read(data->dev, MPL115_PADC); if (ret < 0) goto done; padc = ret >> 6; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_TADC); + ret = data->ops->read(data->dev, MPL115_TADC); if (ret < 0) goto done; tadc = ret >> 6; @@ -90,7 +91,7 @@ static int mpl115_read_temp(struct mpl115_data *data) ret = mpl115_request(data); if (ret < 0) goto done; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_TADC); + ret = data->ops->read(data->dev, MPL115_TADC); done: mutex_unlock(&data->lock); return ret; @@ -145,66 +146,53 @@ static const struct iio_info mpl115_info = { .driver_module = THIS_MODULE, }; -static int mpl115_probe(struct i2c_client *client, - const struct i2c_device_id *id) +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops) { struct mpl115_data *data; struct iio_dev *indio_dev; int ret; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; - - indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - data->client = client; + data->dev = dev; + data->ops = ops; mutex_init(&data->lock); - i2c_set_clientdata(client, indio_dev); indio_dev->info = &mpl115_info; - indio_dev->name = id->name; - indio_dev->dev.parent = &client->dev; + indio_dev->name = name; + indio_dev->dev.parent = dev; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = mpl115_channels; indio_dev->num_channels = ARRAY_SIZE(mpl115_channels); - ret = i2c_smbus_read_word_swapped(data->client, MPL115_A0); + ret = data->ops->init(data->dev); + if (ret) + return ret; + + ret = data->ops->read(data->dev, MPL115_A0); if (ret < 0) return ret; data->a0 = ret; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_B1); + ret = data->ops->read(data->dev, MPL115_B1); if (ret < 0) return ret; data->b1 = ret; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_B2); + ret = data->ops->read(data->dev, MPL115_B2); if (ret < 0) return ret; data->b2 = ret; - ret = i2c_smbus_read_word_swapped(data->client, MPL115_C12); + ret = data->ops->read(data->dev, MPL115_C12); if (ret < 0) return ret; data->c12 = ret; - return devm_iio_device_register(&client->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } - -static const struct i2c_device_id mpl115_id[] = { - { "mpl115", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, mpl115_id); - -static struct i2c_driver mpl115_driver = { - .driver = { - .name = "mpl115", - }, - .probe = mpl115_probe, - .id_table = mpl115_id, -}; -module_i2c_driver(mpl115_driver); +EXPORT_SYMBOL_GPL(mpl115_probe); MODULE_AUTHOR("Peter Meerwald "); MODULE_DESCRIPTION("Freescale MPL115 pressure/temperature driver"); diff --git b/drivers/iio/pressure/mpl115.h b/drivers/iio/pressure/mpl115.h new file mode 100644 index 0000000..01b6527 --- /dev/null +++ b/drivers/iio/pressure/mpl115.h @@ -0,0 +1,24 @@ +/* + * Freescale MPL115A pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * Copyright (c) 2016 Akinobu Mita + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + */ + +#ifndef _MPL115_H_ +#define _MPL115_H_ + +struct mpl115_ops { + int (*init)(struct device *); + int (*read)(struct device *, u8); + int (*write)(struct device *, u8, u8); +}; + +int mpl115_probe(struct device *dev, const char *name, + const struct mpl115_ops *ops); + +#endif diff --git b/drivers/iio/pressure/mpl115_i2c.c b/drivers/iio/pressure/mpl115_i2c.c new file mode 100644 index 0000000..1a29be4 --- /dev/null +++ b/drivers/iio/pressure/mpl115_i2c.c @@ -0,0 +1,67 @@ +/* + * Freescale MPL115A2 pressure/temperature sensor + * + * Copyright (c) 2014 Peter Meerwald + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * (7-bit I2C slave address 0x60) + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A2.pdf + */ + +#include +#include + +#include "mpl115.h" + +static int mpl115_i2c_init(struct device *dev) +{ + return 0; +} + +static int mpl115_i2c_read(struct device *dev, u8 address) +{ + return i2c_smbus_read_word_swapped(to_i2c_client(dev), address); +} + +static int mpl115_i2c_write(struct device *dev, u8 address, u8 value) +{ + return i2c_smbus_write_byte_data(to_i2c_client(dev), address, value); +} + +static const struct mpl115_ops mpl115_i2c_ops = { + .init = mpl115_i2c_init, + .read = mpl115_i2c_read, + .write = mpl115_i2c_write, +}; + +static int mpl115_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -EOPNOTSUPP; + + return mpl115_probe(&client->dev, id->name, &mpl115_i2c_ops); +} + +static const struct i2c_device_id mpl115_i2c_id[] = { + { "mpl115", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpl115_i2c_id); + +static struct i2c_driver mpl115_i2c_driver = { + .driver = { + .name = "mpl115", + }, + .probe = mpl115_i2c_probe, + .id_table = mpl115_i2c_id, +}; +module_i2c_driver(mpl115_i2c_driver); + +MODULE_AUTHOR("Peter Meerwald "); +MODULE_DESCRIPTION("Freescale MPL115A2 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git b/drivers/iio/pressure/mpl115_spi.c b/drivers/iio/pressure/mpl115_spi.c new file mode 100644 index 0000000..9ebf55f --- /dev/null +++ b/drivers/iio/pressure/mpl115_spi.c @@ -0,0 +1,106 @@ +/* + * Freescale MPL115A1 pressure/temperature sensor + * + * Copyright (c) 2016 Akinobu Mita + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * Datasheet: http://www.nxp.com/files/sensors/doc/data_sheet/MPL115A1.pdf + */ + +#include +#include + +#include "mpl115.h" + +#define MPL115_SPI_WRITE(address) ((address) << 1) +#define MPL115_SPI_READ(address) (0x80 | (address) << 1) + +struct mpl115_spi_buf { + u8 tx[4]; + u8 rx[4]; +}; + +static int mpl115_spi_init(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf; + + buf = devm_kzalloc(dev, sizeof(*buf), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + spi_set_drvdata(spi, buf); + + return 0; +} + +static int mpl115_spi_read(struct device *dev, u8 address) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .rx_buf = buf->rx, + .len = 4, + }; + int ret; + + buf->tx[0] = MPL115_SPI_READ(address); + buf->tx[2] = MPL115_SPI_READ(address + 1); + + ret = spi_sync_transfer(spi, &xfer, 1); + if (ret) + return ret; + + return (buf->rx[1] << 8) | buf->rx[3]; +} + +static int mpl115_spi_write(struct device *dev, u8 address, u8 value) +{ + struct spi_device *spi = to_spi_device(dev); + struct mpl115_spi_buf *buf = spi_get_drvdata(spi); + struct spi_transfer xfer = { + .tx_buf = buf->tx, + .len = 2, + }; + + buf->tx[0] = MPL115_SPI_WRITE(address); + buf->tx[1] = value; + + return spi_sync_transfer(spi, &xfer, 1); +} + +static const struct mpl115_ops mpl115_spi_ops = { + .init = mpl115_spi_init, + .read = mpl115_spi_read, + .write = mpl115_spi_write, +}; + +static int mpl115_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + + return mpl115_probe(&spi->dev, id->name, &mpl115_spi_ops); +} + +static const struct spi_device_id mpl115_spi_ids[] = { + { "mpl115", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, mpl115_spi_ids); + +static struct spi_driver mpl115_spi_driver = { + .driver = { + .name = "mpl115", + }, + .probe = mpl115_spi_probe, + .id_table = mpl115_spi_ids, +}; +module_spi_driver(mpl115_spi_driver); + +MODULE_AUTHOR("Akinobu Mita "); +MODULE_DESCRIPTION("Freescale MPL115A1 pressure/temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/pressure/ms5611.h b/drivers/iio/pressure/ms5611.h index 23b93c7..ccda63c 100644 --- a/drivers/iio/pressure/ms5611.h +++ b/drivers/iio/pressure/ms5611.h @@ -16,15 +16,11 @@ #include #include +struct regulator; + #define MS5611_RESET 0x1e #define MS5611_READ_ADC 0x00 #define MS5611_READ_PROM_WORD 0xA0 -#define MS5611_START_TEMP_CONV 0x58 -#define MS5611_START_PRESSURE_CONV 0x48 - -#define MS5611_CONV_TIME_MIN 9040 -#define MS5611_CONV_TIME_MAX 10000 - #define MS5611_PROM_WORDS_NB 8 enum { @@ -39,18 +35,35 @@ struct ms5611_chip_info { s32 *temp, s32 *pressure); }; +/* + * OverSampling Rate descriptor. + * Warning: cmd MUST be kept aligned on a word boundary (see + * m5611_spi_read_adc_temp_and_pressure in ms5611_spi.c). + */ +struct ms5611_osr { + unsigned long conv_usec; + u8 cmd; + unsigned short rate; +}; + struct ms5611_state { void *client; struct mutex lock; + const struct ms5611_osr *pressure_osr; + const struct ms5611_osr *temp_osr; + int (*reset)(struct device *dev); int (*read_prom_word)(struct device *dev, int index, u16 *word); int (*read_adc_temp_and_pressure)(struct device *dev, s32 *temp, s32 *pressure); struct ms5611_chip_info *chip_info; + struct regulator *vdd; }; -int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, int type); +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char* name, int type); +int ms5611_remove(struct iio_dev *indio_dev); #endif /* _MS5611_H */ diff --git a/drivers/iio/pressure/ms5611_core.c b/drivers/iio/pressure/ms5611_core.c index 2f3d9b4..76578b0 100644 --- a/drivers/iio/pressure/ms5611_core.c +++ b/drivers/iio/pressure/ms5611_core.c @@ -16,9 +16,46 @@ #include #include #include +#include +#include +#include +#include +#include #include "ms5611.h" +#define MS5611_INIT_OSR(_cmd, _conv_usec, _rate) \ + { .cmd = _cmd, .conv_usec = _conv_usec, .rate = _rate } + +static const struct ms5611_osr ms5611_avail_pressure_osr[] = { + MS5611_INIT_OSR(0x40, 600, 256), + MS5611_INIT_OSR(0x42, 1170, 512), + MS5611_INIT_OSR(0x44, 2280, 1024), + MS5611_INIT_OSR(0x46, 4540, 2048), + MS5611_INIT_OSR(0x48, 9040, 4096) +}; + +static const struct ms5611_osr ms5611_avail_temp_osr[] = { + MS5611_INIT_OSR(0x50, 600, 256), + MS5611_INIT_OSR(0x52, 1170, 512), + MS5611_INIT_OSR(0x54, 2280, 1024), + MS5611_INIT_OSR(0x56, 4540, 2048), + MS5611_INIT_OSR(0x58, 9040, 4096) +}; + +static const char ms5611_show_osr[] = "256 512 1024 2048 4096"; + +static IIO_CONST_ATTR(oversampling_ratio_available, ms5611_show_osr); + +static struct attribute *ms5611_attributes[] = { + &iio_const_attr_oversampling_ratio_available.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ms5611_attribute_group = { + .attrs = ms5611_attributes, +}; + static bool ms5611_prom_is_valid(u16 *prom, size_t len) { int i, j; @@ -133,17 +170,17 @@ static int ms5607_temp_and_pressure_compensate(struct ms5611_chip_info *chip_inf t = 2000 + ((chip_info->prom[6] * dt) >> 23); if (t < 2000) { - s64 off2, sens2, t2; + s64 off2, sens2, t2, tmp; t2 = (dt * dt) >> 31; - off2 = (61 * (t - 2000) * (t - 2000)) >> 4; - sens2 = off2 << 1; + tmp = (t - 2000) * (t - 2000); + off2 = (61 * tmp) >> 4; + sens2 = tmp << 1; if (t < -1500) { - s64 tmp = (t + 1500) * (t + 1500); - + tmp = (t + 1500) * (t + 1500); off2 += 15 * tmp; - sens2 += (8 * tmp); + sens2 += 8 * tmp; } t -= t2; @@ -173,6 +210,28 @@ static int ms5611_reset(struct iio_dev *indio_dev) return 0; } +static irqreturn_t ms5611_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ms5611_state *st = iio_priv(indio_dev); + s32 buf[4]; /* s32 (pressure) + s32 (temp) + 2 * s32 (timestamp) */ + int ret; + + mutex_lock(&st->lock); + ret = ms5611_read_temp_and_pressure(indio_dev, &buf[1], &buf[0]); + mutex_unlock(&st->lock); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_timestamp(indio_dev, buf, iio_get_time_ns()); + +err: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + static int ms5611_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) @@ -201,11 +260,84 @@ static int ms5611_read_raw(struct iio_dev *indio_dev, default: return -EINVAL; } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_TEMP: + *val = 10; + return IIO_VAL_INT; + case IIO_PRESSURE: + *val = 0; + *val2 = 1000; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + if (chan->type != IIO_TEMP && chan->type != IIO_PRESSURE) + break; + mutex_lock(&st->lock); + if (chan->type == IIO_TEMP) + *val = (int)st->temp_osr->rate; + else + *val = (int)st->pressure_osr->rate; + mutex_unlock(&st->lock); + return IIO_VAL_INT; } return -EINVAL; } +static const struct ms5611_osr *ms5611_find_osr(int rate, + const struct ms5611_osr *osr, + size_t count) +{ + unsigned int r; + + for (r = 0; r < count; r++) + if ((unsigned short)rate == osr[r].rate) + break; + if (r >= count) + return NULL; + return &osr[r]; +} + +static int ms5611_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ms5611_state *st = iio_priv(indio_dev); + const struct ms5611_osr *osr = NULL; + + if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) + return -EINVAL; + + if (chan->type == IIO_TEMP) + osr = ms5611_find_osr(val, ms5611_avail_temp_osr, + ARRAY_SIZE(ms5611_avail_temp_osr)); + else if (chan->type == IIO_PRESSURE) + osr = ms5611_find_osr(val, ms5611_avail_pressure_osr, + ARRAY_SIZE(ms5611_avail_pressure_osr)); + if (!osr) + return -EINVAL; + + mutex_lock(&st->lock); + + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&st->lock); + return -EBUSY; + } + + if (chan->type == IIO_TEMP) + st->temp_osr = osr; + else + st->pressure_osr = osr; + + mutex_unlock(&st->lock); + return 0; +} + +static const unsigned long ms5611_scan_masks[] = {0x3, 0}; + static struct ms5611_chip_info chip_info_tbl[] = { [MS5611] = { .temp_and_pressure_compensate = ms5611_temp_and_pressure_compensate, @@ -218,52 +350,142 @@ static struct ms5611_chip_info chip_info_tbl[] = { static const struct iio_chan_spec ms5611_channels[] = { { .type = IIO_PRESSURE, - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 0, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, }, { .type = IIO_TEMP, - .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), - } + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), + .scan_index = 1, + .scan_type = { + .sign = 's', + .realbits = 32, + .storagebits = 32, + .endianness = IIO_CPU, + }, + }, + IIO_CHAN_SOFT_TIMESTAMP(2), }; static const struct iio_info ms5611_info = { .read_raw = &ms5611_read_raw, + .write_raw = &ms5611_write_raw, + .attrs = &ms5611_attribute_group, .driver_module = THIS_MODULE, }; static int ms5611_init(struct iio_dev *indio_dev) { int ret; + struct ms5611_state *st = iio_priv(indio_dev); + + /* Enable attached regulator if any. */ + st->vdd = devm_regulator_get(indio_dev->dev.parent, "vdd"); + if (!IS_ERR(st->vdd)) { + ret = regulator_enable(st->vdd); + if (ret) { + dev_err(indio_dev->dev.parent, + "failed to enable Vdd supply: %d\n", ret); + return ret; + } + } else { + ret = PTR_ERR(st->vdd); + if (ret != -ENODEV) + return ret; + } ret = ms5611_reset(indio_dev); if (ret < 0) - return ret; + goto err_regulator_disable; - return ms5611_read_prom(indio_dev); + ret = ms5611_read_prom(indio_dev); + if (ret < 0) + goto err_regulator_disable; + + return 0; + +err_regulator_disable: + if (!IS_ERR_OR_NULL(st->vdd)) + regulator_disable(st->vdd); + return ret; +} + +static void ms5611_fini(const struct iio_dev *indio_dev) +{ + const struct ms5611_state *st = iio_priv(indio_dev); + + if (!IS_ERR_OR_NULL(st->vdd)) + regulator_disable(st->vdd); } -int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, int type) +int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, + const char *name, int type) { int ret; struct ms5611_state *st = iio_priv(indio_dev); mutex_init(&st->lock); st->chip_info = &chip_info_tbl[type]; + st->temp_osr = + &ms5611_avail_temp_osr[ARRAY_SIZE(ms5611_avail_temp_osr) - 1]; + st->pressure_osr = + &ms5611_avail_pressure_osr[ARRAY_SIZE(ms5611_avail_pressure_osr) + - 1]; indio_dev->dev.parent = dev; - indio_dev->name = dev->driver->name; + indio_dev->name = name; indio_dev->info = &ms5611_info; indio_dev->channels = ms5611_channels; indio_dev->num_channels = ARRAY_SIZE(ms5611_channels); indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->available_scan_masks = ms5611_scan_masks; ret = ms5611_init(indio_dev); if (ret < 0) return ret; - return devm_iio_device_register(dev, indio_dev); + ret = iio_triggered_buffer_setup(indio_dev, NULL, + ms5611_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + goto err_fini; + } + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(dev, "unable to register iio device\n"); + goto err_buffer_cleanup; + } + + return 0; + +err_buffer_cleanup: + iio_triggered_buffer_cleanup(indio_dev); +err_fini: + ms5611_fini(indio_dev); + return ret; } EXPORT_SYMBOL(ms5611_probe); +int ms5611_remove(struct iio_dev *indio_dev) +{ + iio_device_unregister(indio_dev); + iio_triggered_buffer_cleanup(indio_dev); + ms5611_fini(indio_dev); + + return 0; +} +EXPORT_SYMBOL(ms5611_remove); + MODULE_AUTHOR("Tomasz Duszynski "); MODULE_DESCRIPTION("MS5611 core driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/pressure/ms5611_i2c.c b/drivers/iio/pressure/ms5611_i2c.c index 245797d..55fb5fc 100644 --- a/drivers/iio/pressure/ms5611_i2c.c +++ b/drivers/iio/pressure/ms5611_i2c.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "ms5611.h" @@ -62,23 +63,23 @@ static int ms5611_i2c_read_adc_temp_and_pressure(struct device *dev, { int ret; struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + const struct ms5611_osr *osr = st->temp_osr; - ret = i2c_smbus_write_byte(st->client, MS5611_START_TEMP_CONV); + ret = i2c_smbus_write_byte(st->client, osr->cmd); if (ret < 0) return ret; - usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); - + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); ret = ms5611_i2c_read_adc(st, temp); if (ret < 0) return ret; - ret = i2c_smbus_write_byte(st->client, MS5611_START_PRESSURE_CONV); + osr = st->pressure_osr; + ret = i2c_smbus_write_byte(st->client, osr->cmd); if (ret < 0) return ret; - usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); - + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); return ms5611_i2c_read_adc(st, pressure); } @@ -92,21 +93,38 @@ static int ms5611_i2c_probe(struct i2c_client *client, I2C_FUNC_SMBUS_WRITE_BYTE | I2C_FUNC_SMBUS_READ_WORD_DATA | I2C_FUNC_SMBUS_READ_I2C_BLOCK)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*st)); if (!indio_dev) return -ENOMEM; st = iio_priv(indio_dev); + i2c_set_clientdata(client, indio_dev); st->reset = ms5611_i2c_reset; st->read_prom_word = ms5611_i2c_read_prom_word; st->read_adc_temp_and_pressure = ms5611_i2c_read_adc_temp_and_pressure; st->client = client; - return ms5611_probe(indio_dev, &client->dev, id->driver_data); + return ms5611_probe(indio_dev, &client->dev, id->name, id->driver_data); +} + +static int ms5611_i2c_remove(struct i2c_client *client) +{ + return ms5611_remove(i2c_get_clientdata(client)); } +#if defined(CONFIG_OF) +static const struct of_device_id ms5611_i2c_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "ms5611" }, + { .compatible = "meas,ms5607" }, + { .compatible = "ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_i2c_matches); +#endif + static const struct i2c_device_id ms5611_id[] = { { "ms5611", MS5611 }, { "ms5607", MS5607 }, @@ -117,9 +135,11 @@ MODULE_DEVICE_TABLE(i2c, ms5611_id); static struct i2c_driver ms5611_driver = { .driver = { .name = "ms5611", + .of_match_table = of_match_ptr(ms5611_i2c_matches) }, .id_table = ms5611_id, .probe = ms5611_i2c_probe, + .remove = ms5611_i2c_remove, }; module_i2c_driver(ms5611_driver); diff --git a/drivers/iio/pressure/ms5611_spi.c b/drivers/iio/pressure/ms5611_spi.c index aaa0c4b..932e050 100644 --- a/drivers/iio/pressure/ms5611_spi.c +++ b/drivers/iio/pressure/ms5611_spi.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "ms5611.h" @@ -55,28 +56,29 @@ static int ms5611_spi_read_adc(struct device *dev, s32 *val) static int ms5611_spi_read_adc_temp_and_pressure(struct device *dev, s32 *temp, s32 *pressure) { - u8 cmd; int ret; struct ms5611_state *st = iio_priv(dev_to_iio_dev(dev)); + const struct ms5611_osr *osr = st->temp_osr; - cmd = MS5611_START_TEMP_CONV; - ret = spi_write_then_read(st->client, &cmd, 1, NULL, 0); + /* + * Warning: &osr->cmd MUST be aligned on a word boundary since used as + * 2nd argument (void*) of spi_write_then_read. + */ + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); if (ret < 0) return ret; - usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); - + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); ret = ms5611_spi_read_adc(dev, temp); if (ret < 0) return ret; - cmd = MS5611_START_PRESSURE_CONV; - ret = spi_write_then_read(st->client, &cmd, 1, NULL, 0); + osr = st->pressure_osr; + ret = spi_write_then_read(st->client, &osr->cmd, 1, NULL, 0); if (ret < 0) return ret; - usleep_range(MS5611_CONV_TIME_MIN, MS5611_CONV_TIME_MAX); - + usleep_range(osr->conv_usec, osr->conv_usec + (osr->conv_usec / 10UL)); return ms5611_spi_read_adc(dev, pressure); } @@ -90,6 +92,8 @@ static int ms5611_spi_probe(struct spi_device *spi) if (!indio_dev) return -ENOMEM; + spi_set_drvdata(spi, indio_dev); + spi->mode = SPI_MODE_0; spi->max_speed_hz = 20000000; spi->bits_per_word = 8; @@ -103,10 +107,26 @@ static int ms5611_spi_probe(struct spi_device *spi) st->read_adc_temp_and_pressure = ms5611_spi_read_adc_temp_and_pressure; st->client = spi; - return ms5611_probe(indio_dev, &spi->dev, + return ms5611_probe(indio_dev, &spi->dev, spi_get_device_id(spi)->name, spi_get_device_id(spi)->driver_data); } +static int ms5611_spi_remove(struct spi_device *spi) +{ + return ms5611_remove(spi_get_drvdata(spi)); +} + +#if defined(CONFIG_OF) +static const struct of_device_id ms5611_spi_matches[] = { + { .compatible = "meas,ms5611" }, + { .compatible = "ms5611" }, + { .compatible = "meas,ms5607" }, + { .compatible = "ms5607" }, + { } +}; +MODULE_DEVICE_TABLE(of, ms5611_spi_matches); +#endif + static const struct spi_device_id ms5611_id[] = { { "ms5611", MS5611 }, { "ms5607", MS5607 }, @@ -117,9 +137,11 @@ MODULE_DEVICE_TABLE(spi, ms5611_id); static struct spi_driver ms5611_driver = { .driver = { .name = "ms5611", + .of_match_table = of_match_ptr(ms5611_spi_matches) }, .id_table = ms5611_id, .probe = ms5611_spi_probe, + .remove = ms5611_spi_remove, }; module_spi_driver(ms5611_driver); diff --git a/drivers/iio/pressure/ms5637.c b/drivers/iio/pressure/ms5637.c index e8d0e0d..e68052c 100644 --- a/drivers/iio/pressure/ms5637.c +++ b/drivers/iio/pressure/ms5637.c @@ -136,7 +136,7 @@ static int ms5637_probe(struct i2c_client *client, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { dev_err(&client->dev, "Adapter does not support some i2c transaction\n"); - return -ENODEV; + return -EOPNOTSUPP; } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); diff --git a/drivers/iio/pressure/st_pressure_buffer.c b/drivers/iio/pressure/st_pressure_buffer.c index 2ff53f2..99468d0 100644 --- a/drivers/iio/pressure/st_pressure_buffer.c +++ b/drivers/iio/pressure/st_pressure_buffer.c @@ -82,7 +82,7 @@ static const struct iio_buffer_setup_ops st_press_buffer_setup_ops = { int st_press_allocate_ring(struct iio_dev *indio_dev) { - return iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time, + return iio_triggered_buffer_setup(indio_dev, NULL, &st_sensors_trigger_handler, &st_press_buffer_setup_ops); } diff --git a/drivers/iio/pressure/st_pressure_core.c b/drivers/iio/pressure/st_pressure_core.c index 5056bd6..92a118c 100644 --- a/drivers/iio/pressure/st_pressure_core.c +++ b/drivers/iio/pressure/st_pressure_core.c @@ -65,6 +65,10 @@ #define ST_PRESS_LPS331AP_DRDY_IRQ_ADDR 0x22 #define ST_PRESS_LPS331AP_DRDY_IRQ_INT1_MASK 0x04 #define ST_PRESS_LPS331AP_DRDY_IRQ_INT2_MASK 0x20 +#define ST_PRESS_LPS331AP_IHL_IRQ_ADDR 0x22 +#define ST_PRESS_LPS331AP_IHL_IRQ_MASK 0x80 +#define ST_PRESS_LPS331AP_OD_IRQ_ADDR 0x22 +#define ST_PRESS_LPS331AP_OD_IRQ_MASK 0x40 #define ST_PRESS_LPS331AP_MULTIREAD_BIT true /* CUSTOM VALUES FOR LPS001WP SENSOR */ @@ -105,6 +109,10 @@ #define ST_PRESS_LPS25H_DRDY_IRQ_ADDR 0x23 #define ST_PRESS_LPS25H_DRDY_IRQ_INT1_MASK 0x01 #define ST_PRESS_LPS25H_DRDY_IRQ_INT2_MASK 0x10 +#define ST_PRESS_LPS25H_IHL_IRQ_ADDR 0x22 +#define ST_PRESS_LPS25H_IHL_IRQ_MASK 0x80 +#define ST_PRESS_LPS25H_OD_IRQ_ADDR 0x22 +#define ST_PRESS_LPS25H_OD_IRQ_MASK 0x40 #define ST_PRESS_LPS25H_MULTIREAD_BIT true #define ST_PRESS_LPS25H_OUT_XL_ADDR 0x28 #define ST_TEMP_LPS25H_OUT_L_ADDR 0x2b @@ -229,6 +237,11 @@ static const struct st_sensor_settings st_press_sensors_settings[] = { .addr = ST_PRESS_LPS331AP_DRDY_IRQ_ADDR, .mask_int1 = ST_PRESS_LPS331AP_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_PRESS_LPS331AP_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_PRESS_LPS331AP_IHL_IRQ_ADDR, + .mask_ihl = ST_PRESS_LPS331AP_IHL_IRQ_MASK, + .addr_od = ST_PRESS_LPS331AP_OD_IRQ_ADDR, + .mask_od = ST_PRESS_LPS331AP_OD_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_PRESS_LPS331AP_MULTIREAD_BIT, .bootime = 2, @@ -324,6 +337,11 @@ static const struct st_sensor_settings st_press_sensors_settings[] = { .addr = ST_PRESS_LPS25H_DRDY_IRQ_ADDR, .mask_int1 = ST_PRESS_LPS25H_DRDY_IRQ_INT1_MASK, .mask_int2 = ST_PRESS_LPS25H_DRDY_IRQ_INT2_MASK, + .addr_ihl = ST_PRESS_LPS25H_IHL_IRQ_ADDR, + .mask_ihl = ST_PRESS_LPS25H_IHL_IRQ_MASK, + .addr_od = ST_PRESS_LPS25H_OD_IRQ_ADDR, + .mask_od = ST_PRESS_LPS25H_OD_IRQ_MASK, + .addr_stat_drdy = ST_SENSORS_DEFAULT_STAT_ADDR, }, .multi_read_bit = ST_PRESS_LPS25H_MULTIREAD_BIT, .bootime = 2, @@ -427,6 +445,7 @@ static const struct iio_info press_info = { static const struct iio_trigger_ops st_press_trigger_ops = { .owner = THIS_MODULE, .set_trigger_state = ST_PRESS_TRIGGER_SET_STATE, + .validate_device = st_sensors_validate_device, }; #define ST_PRESS_TRIGGER_OPS (&st_press_trigger_ops) #else diff --git a/drivers/iio/pressure/t5403.c b/drivers/iio/pressure/t5403.c index e11cd39..2667e71 100644 --- a/drivers/iio/pressure/t5403.c +++ b/drivers/iio/pressure/t5403.c @@ -221,7 +221,7 @@ static int t5403_probe(struct i2c_client *client, if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_I2C_BLOCK)) - return -ENODEV; + return -EOPNOTSUPP; ret = i2c_smbus_read_byte_data(client, T5403_SLAVE_ADDR); if (ret < 0) diff --git a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c index e544fcf..4f50238 100644 --- a/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c +++ b/drivers/iio/proximity/pulsedlight-lidar-lite-v2.c @@ -13,7 +13,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * TODO: runtime pm, interrupt mode, and signal strength reporting + * TODO: interrupt mode, and signal strength reporting */ #include @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -35,8 +36,11 @@ #define LIDAR_REG_STATUS_INVALID BIT(3) #define LIDAR_REG_STATUS_READY BIT(0) -#define LIDAR_REG_DATA_HBYTE 0x0f -#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_HBYTE 0x0f +#define LIDAR_REG_DATA_LBYTE 0x10 +#define LIDAR_REG_DATA_WORD_READ BIT(7) + +#define LIDAR_REG_PWR_CONTROL 0x65 #define LIDAR_DRV_NAME "lidar" @@ -44,6 +48,9 @@ struct lidar_data { struct iio_dev *indio_dev; struct i2c_client *client; + int (*xfer)(struct lidar_data *data, u8 reg, u8 *val, int len); + int i2c_enabled; + u16 buffer[8]; /* 2 byte distance + 8 byte timestamp */ }; @@ -62,7 +69,28 @@ static const struct iio_chan_spec lidar_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(1), }; -static int lidar_read_byte(struct lidar_data *data, int reg) +static int lidar_i2c_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) +{ + struct i2c_client *client = data->client; + struct i2c_msg msg[2]; + int ret; + + msg[0].addr = client->addr; + msg[0].flags = client->flags | I2C_M_STOP; + msg[0].len = 1; + msg[0].buf = (char *) ® + + msg[1].addr = client->addr; + msg[1].flags = client->flags | I2C_M_RD; + msg[1].len = len; + msg[1].buf = (char *) val; + + ret = i2c_transfer(client->adapter, msg, 2); + + return (ret == 2) ? 0 : -EIO; +} + +static int lidar_smbus_xfer(struct lidar_data *data, u8 reg, u8 *val, int len) { struct i2c_client *client = data->client; int ret; @@ -72,17 +100,35 @@ static int lidar_read_byte(struct lidar_data *data, int reg) * so in turn i2c_smbus_read_byte_data cannot be used */ - ret = i2c_smbus_write_byte(client, reg); - if (ret < 0) { - dev_err(&client->dev, "cannot write addr value"); - return ret; + while (len--) { + ret = i2c_smbus_write_byte(client, reg++); + if (ret < 0) { + dev_err(&client->dev, "cannot write addr value"); + return ret; + } + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "cannot read data value"); + return ret; + } + + *(val++) = ret; } - ret = i2c_smbus_read_byte(client); + return 0; +} + +static int lidar_read_byte(struct lidar_data *data, u8 reg) +{ + int ret; + u8 val; + + ret = data->xfer(data, reg, &val, 1); if (ret < 0) - dev_err(&client->dev, "cannot read data value"); + return ret; - return ret; + return val; } static inline int lidar_write_control(struct lidar_data *data, int val) @@ -90,24 +136,22 @@ static inline int lidar_write_control(struct lidar_data *data, int val) return i2c_smbus_write_byte_data(data->client, LIDAR_REG_CONTROL, val); } -static int lidar_read_measurement(struct lidar_data *data, u16 *reg) +static inline int lidar_write_power(struct lidar_data *data, int val) { - int ret; - int val; - - ret = lidar_read_byte(data, LIDAR_REG_DATA_HBYTE); - if (ret < 0) - return ret; - val = ret << 8; + return i2c_smbus_write_byte_data(data->client, + LIDAR_REG_PWR_CONTROL, val); +} - ret = lidar_read_byte(data, LIDAR_REG_DATA_LBYTE); - if (ret < 0) - return ret; +static int lidar_read_measurement(struct lidar_data *data, u16 *reg) +{ + int ret = data->xfer(data, LIDAR_REG_DATA_HBYTE | + (data->i2c_enabled ? LIDAR_REG_DATA_WORD_READ : 0), + (u8 *) reg, 2); - val |= ret; - *reg = val; + if (!ret) + *reg = be16_to_cpu(*reg); - return 0; + return ret; } static int lidar_get_measurement(struct lidar_data *data, u16 *reg) @@ -116,6 +160,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) int tries = 10; int ret; + pm_runtime_get_sync(&client->dev); + /* start sample */ ret = lidar_write_control(data, LIDAR_REG_CONTROL_ACQUIRE); if (ret < 0) { @@ -144,6 +190,8 @@ static int lidar_get_measurement(struct lidar_data *data, u16 *reg) } ret = -EIO; } + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return ret; } @@ -221,6 +269,16 @@ static int lidar_probe(struct i2c_client *client, indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; + data = iio_priv(indio_dev); + + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + data->xfer = lidar_i2c_xfer; + data->i2c_enabled = 1; + } else if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BYTE)) + data->xfer = lidar_smbus_xfer; + else + return -EOPNOTSUPP; indio_dev->info = &lidar_info; indio_dev->name = LIDAR_DRV_NAME; @@ -228,7 +286,6 @@ static int lidar_probe(struct i2c_client *client, indio_dev->num_channels = ARRAY_SIZE(lidar_channels); indio_dev->modes = INDIO_DIRECT_MODE; - data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); data->client = client; @@ -243,6 +300,17 @@ static int lidar_probe(struct i2c_client *client, if (ret) goto error_unreg_buffer; + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + + ret = pm_runtime_set_active(&client->dev); + if (ret) + goto error_unreg_buffer; + pm_runtime_enable(&client->dev); + + pm_runtime_mark_last_busy(&client->dev); + pm_runtime_idle(&client->dev); + return 0; error_unreg_buffer: @@ -258,6 +326,9 @@ static int lidar_remove(struct i2c_client *client) iio_device_unregister(indio_dev); iio_triggered_buffer_cleanup(indio_dev); + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + return 0; } @@ -273,10 +344,38 @@ static const struct of_device_id lidar_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, lidar_dt_ids); +#ifdef CONFIG_PM +static int lidar_pm_runtime_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + + return lidar_write_power(data, 0x0f); +} + +static int lidar_pm_runtime_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct lidar_data *data = iio_priv(indio_dev); + int ret = lidar_write_power(data, 0); + + /* regulator and FPGA needs settling time */ + usleep_range(15000, 20000); + + return ret; +} +#endif + +static const struct dev_pm_ops lidar_pm_ops = { + SET_RUNTIME_PM_OPS(lidar_pm_runtime_suspend, + lidar_pm_runtime_resume, NULL) +}; + static struct i2c_driver lidar_driver = { .driver = { .name = LIDAR_DRV_NAME, .of_match_table = of_match_ptr(lidar_dt_ids), + .pm = &lidar_pm_ops, }, .probe = lidar_probe, .remove = lidar_remove, diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c index a570c2e..4b645fc 100644 --- a/drivers/iio/temperature/mlx90614.c +++ b/drivers/iio/temperature/mlx90614.c @@ -516,7 +516,7 @@ static int mlx90614_probe(struct i2c_client *client, int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + return -EOPNOTSUPP; indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); if (!indio_dev) diff --git a/drivers/iio/temperature/tmp006.c b/drivers/iio/temperature/tmp006.c index e78c106..18c9b43 100644 --- a/drivers/iio/temperature/tmp006.c +++ b/drivers/iio/temperature/tmp006.c @@ -205,7 +205,7 @@ static int tmp006_probe(struct i2c_client *client, int ret; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + return -EOPNOTSUPP; if (!tmp006_check_identification(client)) { dev_err(&client->dev, "no TMP006 sensor\n"); diff --git a/drivers/iio/temperature/tsys01.c b/drivers/iio/temperature/tsys01.c index 05c1206..3e60c61 100644 --- a/drivers/iio/temperature/tsys01.c +++ b/drivers/iio/temperature/tsys01.c @@ -190,7 +190,7 @@ static int tsys01_i2c_probe(struct i2c_client *client, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { dev_err(&client->dev, "Adapter does not support some i2c transaction\n"); - return -ENODEV; + return -EOPNOTSUPP; } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); diff --git a/drivers/iio/temperature/tsys02d.c b/drivers/iio/temperature/tsys02d.c index 4c1fbd5..ab6fe8f 100644 --- a/drivers/iio/temperature/tsys02d.c +++ b/drivers/iio/temperature/tsys02d.c @@ -137,7 +137,7 @@ static int tsys02d_probe(struct i2c_client *client, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { dev_err(&client->dev, "Adapter does not support some i2c transaction\n"); - return -ENODEV; + return -EOPNOTSUPP; } indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*dev_data)); diff --git a/drivers/iio/trigger/Kconfig b/drivers/iio/trigger/Kconfig index 7999612..519e677 100644 --- a/drivers/iio/trigger/Kconfig +++ b/drivers/iio/trigger/Kconfig @@ -5,6 +5,16 @@ menu "Triggers - standalone" +config IIO_HRTIMER_TRIGGER + tristate "High resolution timer trigger" + depends on IIO_SW_TRIGGER + help + Provides a frequency based IIO trigger using high resolution + timers as interrupt source. + + To compile this driver as a module, choose M here: the + module will be called iio-trig-hrtimer. + config IIO_INTERRUPT_TRIGGER tristate "Generic interrupt trigger" help diff --git a/drivers/iio/trigger/Makefile b/drivers/iio/trigger/Makefile index 0694dae..fe06eb5 100644 --- a/drivers/iio/trigger/Makefile +++ b/drivers/iio/trigger/Makefile @@ -3,5 +3,7 @@ # # When adding new entries keep the list in alphabetical order + +obj-$(CONFIG_IIO_HRTIMER_TRIGGER) += iio-trig-hrtimer.o obj-$(CONFIG_IIO_INTERRUPT_TRIGGER) += iio-trig-interrupt.o obj-$(CONFIG_IIO_SYSFS_TRIGGER) += iio-trig-sysfs.o diff --git b/drivers/iio/trigger/iio-trig-hrtimer.c b/drivers/iio/trigger/iio-trig-hrtimer.c new file mode 100644 index 0000000..5e6d451 --- /dev/null +++ b/drivers/iio/trigger/iio-trig-hrtimer.c @@ -0,0 +1,193 @@ +/** + * The industrial I/O periodic hrtimer trigger driver + * + * Copyright (C) Intuitive Aerial AB + * Written by Marten Svanfeldt, marten@intuitiveaerial.com + * Copyright (C) 2012, Analog Device Inc. + * Author: Lars-Peter Clausen + * Copyright (C) 2015, Intel Corporation + * + * 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. + * + */ +#include +#include +#include + +#include +#include +#include + +/* default sampling frequency - 100Hz */ +#define HRTIMER_DEFAULT_SAMPLING_FREQUENCY 100 + +struct iio_hrtimer_info { + struct iio_sw_trigger swt; + struct hrtimer timer; + unsigned long sampling_frequency; + ktime_t period; +}; + +static struct config_item_type iio_hrtimer_type = { + .ct_owner = THIS_MODULE, +}; + +static +ssize_t iio_hrtimer_show_sampling_frequency(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + + return snprintf(buf, PAGE_SIZE, "%lu\n", info->sampling_frequency); +} + +static +ssize_t iio_hrtimer_store_sampling_frequency(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_trigger *trig = to_iio_trigger(dev); + struct iio_hrtimer_info *info = iio_trigger_get_drvdata(trig); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + if (!val || val > NSEC_PER_SEC) + return -EINVAL; + + info->sampling_frequency = val; + info->period = ktime_set(0, NSEC_PER_SEC / val); + + return len; +} + +static DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, + iio_hrtimer_show_sampling_frequency, + iio_hrtimer_store_sampling_frequency); + +static struct attribute *iio_hrtimer_attrs[] = { + &dev_attr_sampling_frequency.attr, + NULL +}; + +static const struct attribute_group iio_hrtimer_attr_group = { + .attrs = iio_hrtimer_attrs, +}; + +static const struct attribute_group *iio_hrtimer_attr_groups[] = { + &iio_hrtimer_attr_group, + NULL +}; + +static enum hrtimer_restart iio_hrtimer_trig_handler(struct hrtimer *timer) +{ + struct iio_hrtimer_info *info; + + info = container_of(timer, struct iio_hrtimer_info, timer); + + hrtimer_forward_now(timer, info->period); + iio_trigger_poll(info->swt.trigger); + + return HRTIMER_RESTART; +} + +static int iio_trig_hrtimer_set_state(struct iio_trigger *trig, bool state) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(trig); + + if (state) + hrtimer_start(&trig_info->timer, trig_info->period, + HRTIMER_MODE_REL); + else + hrtimer_cancel(&trig_info->timer); + + return 0; +} + +static const struct iio_trigger_ops iio_hrtimer_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = iio_trig_hrtimer_set_state, +}; + +static struct iio_sw_trigger *iio_trig_hrtimer_probe(const char *name) +{ + struct iio_hrtimer_info *trig_info; + int ret; + + trig_info = kzalloc(sizeof(*trig_info), GFP_KERNEL); + if (!trig_info) + return ERR_PTR(-ENOMEM); + + trig_info->swt.trigger = iio_trigger_alloc("%s", name); + if (!trig_info->swt.trigger) { + ret = -ENOMEM; + goto err_free_trig_info; + } + + iio_trigger_set_drvdata(trig_info->swt.trigger, trig_info); + trig_info->swt.trigger->ops = &iio_hrtimer_trigger_ops; + trig_info->swt.trigger->dev.groups = iio_hrtimer_attr_groups; + + hrtimer_init(&trig_info->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + trig_info->timer.function = iio_hrtimer_trig_handler; + + trig_info->sampling_frequency = HRTIMER_DEFAULT_SAMPLING_FREQUENCY; + trig_info->period = ktime_set(0, NSEC_PER_SEC / + trig_info->sampling_frequency); + + ret = iio_trigger_register(trig_info->swt.trigger); + if (ret) + goto err_free_trigger; + + iio_swt_group_init_type_name(&trig_info->swt, name, &iio_hrtimer_type); + return &trig_info->swt; +err_free_trigger: + iio_trigger_free(trig_info->swt.trigger); +err_free_trig_info: + kfree(trig_info); + + return ERR_PTR(ret); +} + +static int iio_trig_hrtimer_remove(struct iio_sw_trigger *swt) +{ + struct iio_hrtimer_info *trig_info; + + trig_info = iio_trigger_get_drvdata(swt->trigger); + + iio_trigger_unregister(swt->trigger); + + /* cancel the timer after unreg to make sure no one rearms it */ + hrtimer_cancel(&trig_info->timer); + iio_trigger_free(swt->trigger); + kfree(trig_info); + + return 0; +} + +static const struct iio_sw_trigger_ops iio_trig_hrtimer_ops = { + .probe = iio_trig_hrtimer_probe, + .remove = iio_trig_hrtimer_remove, +}; + +static struct iio_sw_trigger_type iio_trig_hrtimer = { + .name = "hrtimer", + .owner = THIS_MODULE, + .ops = &iio_trig_hrtimer_ops, +}; + +module_iio_sw_trigger_driver(iio_trig_hrtimer); + +MODULE_AUTHOR("Marten Svanfeldt "); +MODULE_AUTHOR("Daniel Baluta "); +MODULE_DESCRIPTION("Periodic hrtimer trigger for the IIO subsystem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index ae33da7..2fb1f43 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -295,6 +295,16 @@ config TOUCHSCREEN_EGALAX To compile this driver as a module, choose M here: the module will be called egalax_ts. +config TOUCHSCREEN_EGALAX_SERIAL + tristate "EETI eGalax serial touchscreen" + select SERIO + help + Say Y here to enable support for serial connected EETI + eGalax touch panels. + + To compile this driver as a module, choose M here: the + module will be called egalax_ts_serial. + config TOUCHSCREEN_FT6236 tristate "FT6236 I2C touchscreen" depends on I2C @@ -324,6 +334,7 @@ config TOUCHSCREEN_FUJITSU config TOUCHSCREEN_GOODIX tristate "Goodix I2C touchscreen" depends on I2C + depends on GPIOLIB || COMPILE_TEST help Say Y here if you have the Goodix touchscreen (such as one installed in Onda v975w tablets) connected to your @@ -365,7 +376,7 @@ config TOUCHSCREEN_IPROC config TOUCHSCREEN_S3C2410 tristate "Samsung S3C2410/generic touchscreen input driver" depends on ARCH_S3C24XX || SAMSUNG_DEV_TS - select S3C_ADC + depends on S3C_ADC help Say Y here if you have the s3c2410 touchscreen. @@ -480,6 +491,17 @@ config TOUCHSCREEN_MMS114 To compile this driver as a module, choose M here: the module will be called mms114. +config TOUCHSCREEN_MELFAS_MIP4 + tristate "MELFAS MIP4 Touchscreen" + depends on I2C + help + Say Y here if you have a MELFAS MIP4 Touchscreen device. + + If unsure, say N. + + To compile this driver as a module, choose M here: + the module will be called melfas_mip4. + config TOUCHSCREEN_MTOUCH tristate "MicroTouch serial touchscreens" select SERIO @@ -610,7 +632,7 @@ config TOUCHSCREEN_EDT_FT5X06 config TOUCHSCREEN_MIGOR tristate "Renesas MIGO-R touchscreen" - depends on SH_MIGOR && I2C + depends on (SH_MIGOR || COMPILE_TEST) && I2C help Say Y here to enable MIGO-R touchscreen support. @@ -811,6 +833,15 @@ config TOUCHSCREEN_USB_COMPOSITE To compile this driver as a module, choose M here: the module will be called usbtouchscreen. +config TOUCHSCREEN_MX25 + tristate "Freescale i.MX25 touchscreen input driver" + depends on MFD_MX25_TSADC + help + Enable support for touchscreen connected to your i.MX25. + + To compile this driver as a module, choose M here: the + module will be called fsl-imx25-tcq. + config TOUCHSCREEN_MC13783 tristate "Freescale MC13783 touchscreen input driver" depends on MFD_MC13XXX @@ -927,6 +958,23 @@ config TOUCHSCREEN_TOUCHIT213 To compile this driver as a module, choose M here: the module will be called touchit213. +config TOUCHSCREEN_TS4800 + tristate "TS-4800 touchscreen" + depends on HAS_IOMEM && OF + depends on SOC_IMX51 || COMPILE_TEST + select MFD_SYSCON + select INPUT_POLLDEV + help + Say Y here if you have a touchscreen on a TS-4800 board. + + On TS-4800, the touchscreen is not handled directly by Linux but by + a companion FPGA. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ts4800_ts. + config TOUCHSCREEN_TSC_SERIO tristate "TSC-10/25/40 serial touchscreen support" select SERIO @@ -998,6 +1046,44 @@ config TOUCHSCREEN_PCAP To compile this driver as a module, choose M here: the module will be called pcap_ts. +config TOUCHSCREEN_RM_TS + tristate "Raydium I2C Touchscreen" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + help + Say Y here if you have Raydium series I2C touchscreen, + such as RM32380, connected to your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called raydium_i2c_ts. + +config TOUCHSCREEN_SILEAD + tristate "Silead I2C touchscreen" + depends on I2C + help + Say Y here if you have the Silead touchscreen connected to + your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called silead. + +config TOUCHSCREEN_SIS_I2C + tristate "SiS 9200 family I2C touchscreen" + depends on I2C + select CRC_ITU_T + depends on GPIOLIB || COMPILE_TEST + help + This enables support for SiS 9200 family over I2C based touchscreens. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called sis_i2c. + config TOUCHSCREEN_ST1232 tristate "Sitronix ST1232 touchscreen controllers" depends on I2C @@ -1046,6 +1132,19 @@ config TOUCHSCREEN_SUR40 To compile this driver as a module, choose M here: the module will be called sur40. +config TOUCHSCREEN_SURFACE3_SPI + tristate "Ntrig/Microsoft Surface 3 SPI touchscreen" + depends on SPI + depends on GPIOLIB || COMPILE_TEST + help + Say Y here if you have the Ntrig/Microsoft SPI touchscreen + controller chip as found on the Surface 3 in your system. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called surface3_spi. + config TOUCHSCREEN_SX8654 tristate "Semtech SX8654 touchscreen" depends on I2C @@ -1085,7 +1184,8 @@ config TOUCHSCREEN_ZFORCE config TOUCHSCREEN_COLIBRI_VF50 tristate "Toradex Colibri on board touchscreen driver" - depends on GPIOLIB && IIO && VF610_ADC + depends on IIO && VF610_ADC + depends on GPIOLIB || COMPILE_TEST help Say Y here if you have a Colibri VF50 and plan to use the on-board provided 4-wire touchscreen driver. diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index cbaa6ab..b4373d6 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_TOUCHSCREEN_EETI) += eeti_ts.o obj-$(CONFIG_TOUCHSCREEN_ELAN) += elants_i2c.o obj-$(CONFIG_TOUCHSCREEN_ELO) += elo.o obj-$(CONFIG_TOUCHSCREEN_EGALAX) += egalax_ts.o +obj-$(CONFIG_TOUCHSCREEN_EGALAX_SERIAL) += egalax_ts_serial.o obj-$(CONFIG_TOUCHSCREEN_FT6236) += ft6236.o obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix.o @@ -45,8 +46,10 @@ obj-$(CONFIG_TOUCHSCREEN_INTEL_MID) += intel-mid-touch.o obj-$(CONFIG_TOUCHSCREEN_IPROC) += bcm_iproc_tsc.o obj-$(CONFIG_TOUCHSCREEN_LPC32XX) += lpc32xx_ts.o obj-$(CONFIG_TOUCHSCREEN_MAX11801) += max11801_ts.o +obj-$(CONFIG_TOUCHSCREEN_MX25) += fsl-imx25-tcq.o obj-$(CONFIG_TOUCHSCREEN_MC13783) += mc13783_ts.o obj-$(CONFIG_TOUCHSCREEN_MCS5000) += mcs5000_ts.o +obj-$(CONFIG_TOUCHSCREEN_MELFAS_MIP4) += melfas_mip4.o obj-$(CONFIG_TOUCHSCREEN_MIGOR) += migor_ts.o obj-$(CONFIG_TOUCHSCREEN_MMS114) += mms114.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o @@ -59,15 +62,20 @@ obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_PIXCIR) += pixcir_i2c_ts.o +obj-$(CONFIG_TOUCHSCREEN_RM_TS) += raydium_i2c_ts.o obj-$(CONFIG_TOUCHSCREEN_S3C2410) += s3c2410_ts.o +obj-$(CONFIG_TOUCHSCREEN_SILEAD) += silead.o +obj-$(CONFIG_TOUCHSCREEN_SIS_I2C) += sis_i2c.o obj-$(CONFIG_TOUCHSCREEN_ST1232) += st1232.o obj-$(CONFIG_TOUCHSCREEN_STMPE) += stmpe-ts.o obj-$(CONFIG_TOUCHSCREEN_SUN4I) += sun4i-ts.o obj-$(CONFIG_TOUCHSCREEN_SUR40) += sur40.o +obj-$(CONFIG_TOUCHSCREEN_SURFACE3_SPI) += surface3_spi.o obj-$(CONFIG_TOUCHSCREEN_TI_AM335X_TSC) += ti_am335x_tsc.o obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213) += touchit213.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o +obj-$(CONFIG_TOUCHSCREEN_TS4800) += ts4800-ts.o obj-$(CONFIG_TOUCHSCREEN_TSC_SERIO) += tsc40.o obj-$(CONFIG_TOUCHSCREEN_TSC200X_CORE) += tsc200x-core.o obj-$(CONFIG_TOUCHSCREEN_TSC2004) += tsc2004.o diff --git a/drivers/input/touchscreen/ad7879-i2c.c b/drivers/input/touchscreen/ad7879-i2c.c index d66962c..58f72e0 100644 --- a/drivers/input/touchscreen/ad7879-i2c.c +++ b/drivers/input/touchscreen/ad7879-i2c.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "ad7879.h" @@ -91,10 +92,19 @@ static const struct i2c_device_id ad7879_id[] = { }; MODULE_DEVICE_TABLE(i2c, ad7879_id); +#ifdef CONFIG_OF +static const struct of_device_id ad7879_i2c_dt_ids[] = { + { .compatible = "adi,ad7879-1", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7879_i2c_dt_ids); +#endif + static struct i2c_driver ad7879_i2c_driver = { .driver = { .name = "ad7879", .pm = &ad7879_pm_ops, + .of_match_table = of_match_ptr(ad7879_i2c_dt_ids), }, .probe = ad7879_i2c_probe, .remove = ad7879_i2c_remove, diff --git a/drivers/input/touchscreen/ad7879-spi.c b/drivers/input/touchscreen/ad7879-spi.c index 48033c2..d42b6b9 100644 --- a/drivers/input/touchscreen/ad7879-spi.c +++ b/drivers/input/touchscreen/ad7879-spi.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "ad7879.h" @@ -146,10 +147,19 @@ static int ad7879_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id ad7879_spi_dt_ids[] = { + { .compatible = "adi,ad7879", }, + { } +}; +MODULE_DEVICE_TABLE(of, ad7879_spi_dt_ids); +#endif + static struct spi_driver ad7879_spi_driver = { .driver = { .name = "ad7879", .pm = &ad7879_pm_ops, + .of_match_table = of_match_ptr(ad7879_spi_dt_ids), }, .probe = ad7879_spi_probe, .remove = ad7879_spi_remove, diff --git a/drivers/input/touchscreen/ad7879.c b/drivers/input/touchscreen/ad7879.c index fec66ad..e16a446 100644 --- a/drivers/input/touchscreen/ad7879.c +++ b/drivers/input/touchscreen/ad7879.c @@ -31,7 +31,8 @@ #include #include -#include +#include +#include #include #include "ad7879.h" @@ -94,8 +95,8 @@ #define AD7879_TEMP_BIT (1<<1) enum { - AD7879_SEQ_XPOS = 0, - AD7879_SEQ_YPOS = 1, + AD7879_SEQ_YPOS = 0, + AD7879_SEQ_XPOS = 1, AD7879_SEQ_Z1 = 2, AD7879_SEQ_Z2 = 3, AD7879_NR_SENSE = 4, @@ -126,7 +127,6 @@ struct ad7879 { u8 pen_down_acc_interval; u8 median; u16 x_plate_ohms; - u16 pressure_max; u16 cmd_crtl1; u16 cmd_crtl2; u16 cmd_crtl3; @@ -170,10 +170,10 @@ static int ad7879_report(struct ad7879 *ts) * filter. The combination of these two techniques provides a robust * solution, discarding the spurious noise in the signal and keeping * only the data of interest. The size of both filters is - * programmable. (dev.platform_data, see linux/spi/ad7879.h) Other - * user-programmable conversion controls include variable acquisition - * time, and first conversion delay. Up to 16 averages can be taken - * per conversion. + * programmable. (dev.platform_data, see linux/platform_data/ad7879.h) + * Other user-programmable conversion controls include variable + * acquisition time, and first conversion delay. Up to 16 averages can + * be taken per conversion. */ if (likely(x && z1)) { @@ -186,7 +186,7 @@ static int ad7879_report(struct ad7879 *ts) * Sample found inconsistent, pressure is beyond * the maximum. Don't report it to user space. */ - if (Rt > ts->pressure_max) + if (Rt > input_abs_get_max(input_dev, ABS_PRESSURE)) return -EINVAL; /* @@ -379,7 +379,7 @@ static const struct attribute_group ad7879_attr_group = { static int ad7879_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) { - struct ad7879 *ts = container_of(chip, struct ad7879, gc); + struct ad7879 *ts = gpiochip_get_data(chip); int err; mutex_lock(&ts->mutex); @@ -393,7 +393,7 @@ static int ad7879_gpio_direction_input(struct gpio_chip *chip, static int ad7879_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, int level) { - struct ad7879 *ts = container_of(chip, struct ad7879, gc); + struct ad7879 *ts = gpiochip_get_data(chip); int err; mutex_lock(&ts->mutex); @@ -412,7 +412,7 @@ static int ad7879_gpio_direction_output(struct gpio_chip *chip, static int ad7879_gpio_get_value(struct gpio_chip *chip, unsigned gpio) { - struct ad7879 *ts = container_of(chip, struct ad7879, gc); + struct ad7879 *ts = gpiochip_get_data(chip); u16 val; mutex_lock(&ts->mutex); @@ -425,7 +425,7 @@ static int ad7879_gpio_get_value(struct gpio_chip *chip, unsigned gpio) static void ad7879_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value) { - struct ad7879 *ts = container_of(chip, struct ad7879, gc); + struct ad7879 *ts = gpiochip_get_data(chip); mutex_lock(&ts->mutex); if (value) @@ -454,9 +454,9 @@ static int ad7879_gpio_add(struct ad7879 *ts, ts->gc.ngpio = 1; ts->gc.label = "AD7879-GPIO"; ts->gc.owner = THIS_MODULE; - ts->gc.dev = ts->dev; + ts->gc.parent = ts->dev; - ret = gpiochip_add(&ts->gc); + ret = gpiochip_add_data(&ts->gc, ts); if (ret) dev_err(ts->dev, "failed to register gpio %d\n", ts->gc.base); @@ -469,7 +469,7 @@ static void ad7879_gpio_remove(struct ad7879 *ts) { const struct ad7879_platform_data *pdata = dev_get_platdata(ts->dev); - if (pdata->gpio_export) + if (pdata && pdata->gpio_export) gpiochip_remove(&ts->gc); } @@ -485,6 +485,32 @@ static inline void ad7879_gpio_remove(struct ad7879 *ts) } #endif +static int ad7879_parse_dt(struct device *dev, struct ad7879 *ts) +{ + int err; + u32 tmp; + + err = device_property_read_u32(dev, "adi,resistance-plate-x", &tmp); + if (err) { + dev_err(dev, "failed to get resistance-plate-x property\n"); + return err; + } + ts->x_plate_ohms = (u16)tmp; + + device_property_read_u8(dev, "adi,first-conversion-delay", + &ts->first_conversion_delay); + device_property_read_u8(dev, "adi,acquisition-time", + &ts->acquisition_time); + device_property_read_u8(dev, "adi,median-filter-size", &ts->median); + device_property_read_u8(dev, "adi,averaging", &ts->averaging); + device_property_read_u8(dev, "adi,conversion-interval", + &ts->pen_down_acc_interval); + + ts->swap_xy = device_property_read_bool(dev, "touchscreen-swapped-x-y"); + + return 0; +} + struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, const struct ad7879_bus_ops *bops) { @@ -495,41 +521,44 @@ struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, u16 revid; if (!irq) { - dev_err(dev, "no IRQ?\n"); - err = -EINVAL; - goto err_out; + dev_err(dev, "No IRQ specified\n"); + return ERR_PTR(-EINVAL); } - if (!pdata) { - dev_err(dev, "no platform data?\n"); - err = -EINVAL; - goto err_out; + ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return ERR_PTR(-ENOMEM); + + if (pdata) { + /* Platform data use swapped axis (backward compatibility) */ + ts->swap_xy = !pdata->swap_xy; + + ts->x_plate_ohms = pdata->x_plate_ohms ? : 400; + + ts->first_conversion_delay = pdata->first_conversion_delay; + ts->acquisition_time = pdata->acquisition_time; + ts->averaging = pdata->averaging; + ts->pen_down_acc_interval = pdata->pen_down_acc_interval; + ts->median = pdata->median; + } else if (dev->of_node) { + ad7879_parse_dt(dev, ts); + } else { + dev_err(dev, "No platform data\n"); + return ERR_PTR(-EINVAL); } - ts = kzalloc(sizeof(*ts), GFP_KERNEL); - input_dev = input_allocate_device(); - if (!ts || !input_dev) { - err = -ENOMEM; - goto err_free_mem; + input_dev = devm_input_allocate_device(dev); + if (!input_dev) { + dev_err(dev, "Failed to allocate input device\n"); + return ERR_PTR(-ENOMEM); } ts->bops = bops; ts->dev = dev; ts->input = input_dev; ts->irq = irq; - ts->swap_xy = pdata->swap_xy; setup_timer(&ts->timer, ad7879_timer, (unsigned long) ts); - - ts->x_plate_ohms = pdata->x_plate_ohms ? : 400; - ts->pressure_max = pdata->pressure_max ? : ~0; - - ts->first_conversion_delay = pdata->first_conversion_delay; - ts->acquisition_time = pdata->acquisition_time; - ts->averaging = pdata->averaging; - ts->pen_down_acc_interval = pdata->pen_down_acc_interval; - ts->median = pdata->median; - snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev)); input_dev->name = "AD7879 Touchscreen"; @@ -550,21 +579,33 @@ struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, __set_bit(EV_KEY, input_dev->evbit); __set_bit(BTN_TOUCH, input_dev->keybit); - input_set_abs_params(input_dev, ABS_X, - pdata->x_min ? : 0, - pdata->x_max ? : MAX_12BIT, - 0, 0); - input_set_abs_params(input_dev, ABS_Y, - pdata->y_min ? : 0, - pdata->y_max ? : MAX_12BIT, - 0, 0); - input_set_abs_params(input_dev, ABS_PRESSURE, - pdata->pressure_min, pdata->pressure_max, 0, 0); + if (pdata) { + input_set_abs_params(input_dev, ABS_X, + pdata->x_min ? : 0, + pdata->x_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_Y, + pdata->y_min ? : 0, + pdata->y_max ? : MAX_12BIT, + 0, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, + pdata->pressure_min, + pdata->pressure_max ? : ~0, + 0, 0); + } else { + input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); + touchscreen_parse_properties(input_dev, false, NULL); + if (!input_abs_get_max(input_dev, ABS_PRESSURE)) { + dev_err(dev, "Touchscreen pressure is not specified\n"); + return ERR_PTR(-EINVAL); + } + } err = ad7879_write(ts, AD7879_REG_CTRL2, AD7879_RESET); if (err < 0) { dev_err(dev, "Failed to write %s\n", input_dev->name); - goto err_free_mem; + return ERR_PTR(err); } revid = ad7879_read(ts, AD7879_REG_REVID); @@ -573,8 +614,7 @@ struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, if (input_dev->id.product != devid) { dev_err(dev, "Failed to probe %s (%x vs %x)\n", input_dev->name, devid, revid); - err = -ENODEV; - goto err_free_mem; + return ERR_PTR(-ENODEV); } ts->cmd_crtl3 = AD7879_YPLUS_BIT | @@ -594,23 +634,25 @@ struct ad7879 *ad7879_probe(struct device *dev, u8 devid, unsigned int irq, AD7879_ACQ(ts->acquisition_time) | AD7879_TMR(ts->pen_down_acc_interval); - err = request_threaded_irq(ts->irq, NULL, ad7879_irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), ts); + err = devm_request_threaded_irq(dev, ts->irq, NULL, ad7879_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), ts); if (err) { - dev_err(dev, "irq %d busy?\n", ts->irq); - goto err_free_mem; + dev_err(dev, "Failed to request IRQ: %d\n", err); + return ERR_PTR(err); } __ad7879_disable(ts); err = sysfs_create_group(&dev->kobj, &ad7879_attr_group); if (err) - goto err_free_irq; + goto err_out; - err = ad7879_gpio_add(ts, pdata); - if (err) - goto err_remove_attr; + if (pdata) { + err = ad7879_gpio_add(ts, pdata); + if (err) + goto err_remove_attr; + } err = input_register_device(input_dev); if (err) @@ -622,11 +664,6 @@ err_remove_gpio: ad7879_gpio_remove(ts); err_remove_attr: sysfs_remove_group(&dev->kobj, &ad7879_attr_group); -err_free_irq: - free_irq(ts->irq, ts); -err_free_mem: - input_free_device(input_dev); - kfree(ts); err_out: return ERR_PTR(err); } @@ -636,9 +673,6 @@ void ad7879_remove(struct ad7879 *ts) { ad7879_gpio_remove(ts); sysfs_remove_group(&ts->dev->kobj, &ad7879_attr_group); - free_irq(ts->irq, ts); - input_unregister_device(ts->input); - kfree(ts); } EXPORT_SYMBOL(ad7879_remove); diff --git a/drivers/input/touchscreen/ads7846.c b/drivers/input/touchscreen/ads7846.c index a61b215..1ce3ecb 100644 --- a/drivers/input/touchscreen/ads7846.c +++ b/drivers/input/touchscreen/ads7846.c @@ -1473,7 +1473,6 @@ static int ads7846_remove(struct spi_device *spi) ads784x_hwmon_unregister(spi, ts); - regulator_disable(ts->reg); regulator_put(ts->reg); if (!ts->get_pendown_state) { diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c index 726a83e..29e1b49 100644 --- a/drivers/input/touchscreen/atmel_mxt_ts.c +++ b/drivers/input/touchscreen/atmel_mxt_ts.c @@ -113,8 +113,8 @@ struct t7_config { #define MXT_T9_DETECT (1 << 7) struct t9_range { - u16 x; - u16 y; + __le16 x; + __le16 y; } __packed; /* MXT_TOUCH_MULTI_T9 orient */ @@ -216,6 +216,7 @@ struct mxt_data { unsigned int irq; unsigned int max_x; unsigned int max_y; + bool xy_switch; bool in_bootloader; u16 mem_size; u8 t100_aux_ampl; @@ -1092,6 +1093,19 @@ static int mxt_t6_command(struct mxt_data *data, u16 cmd_offset, return 0; } +static int mxt_acquire_irq(struct mxt_data *data) +{ + int error; + + enable_irq(data->irq); + + error = mxt_process_messages_until_invalid(data); + if (error) + return error; + + return 0; +} + static int mxt_soft_reset(struct mxt_data *data) { struct device *dev = &data->client->dev; @@ -1110,7 +1124,7 @@ static int mxt_soft_reset(struct mxt_data *data) /* Ignore CHG line for 100ms after reset */ msleep(100); - enable_irq(data->irq); + mxt_acquire_irq(data); ret = mxt_wait_for_completion(data, &data->reset_completion, MXT_RESET_TIMEOUT); @@ -1465,19 +1479,6 @@ release_mem: return ret; } -static int mxt_acquire_irq(struct mxt_data *data) -{ - int error; - - enable_irq(data->irq); - - error = mxt_process_messages_until_invalid(data); - if (error) - return error; - - return 0; -} - static int mxt_get_info(struct mxt_data *data) { struct i2c_client *client = data->client; @@ -1665,8 +1666,8 @@ static int mxt_read_t9_resolution(struct mxt_data *data) if (error) return error; - le16_to_cpus(&range.x); - le16_to_cpus(&range.y); + data->max_x = get_unaligned_le16(&range.x); + data->max_y = get_unaligned_le16(&range.y); error = __mxt_read_reg(client, object->start_address + MXT_T9_ORIENT, @@ -1674,23 +1675,7 @@ static int mxt_read_t9_resolution(struct mxt_data *data) if (error) return error; - /* Handle default values */ - if (range.x == 0) - range.x = 1023; - - if (range.y == 0) - range.y = 1023; - - if (orient & MXT_T9_ORIENT_SWITCH) { - data->max_x = range.y; - data->max_y = range.x; - } else { - data->max_x = range.x; - data->max_y = range.y; - } - - dev_dbg(&client->dev, - "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + data->xy_switch = orient & MXT_T9_ORIENT_SWITCH; return 0; } @@ -1708,13 +1693,14 @@ static int mxt_read_t100_config(struct mxt_data *data) if (!object) return -EINVAL; + /* read touchscreen dimensions */ error = __mxt_read_reg(client, object->start_address + MXT_T100_XRANGE, sizeof(range_x), &range_x); if (error) return error; - le16_to_cpus(&range_x); + data->max_x = get_unaligned_le16(&range_x); error = __mxt_read_reg(client, object->start_address + MXT_T100_YRANGE, @@ -1722,36 +1708,24 @@ static int mxt_read_t100_config(struct mxt_data *data) if (error) return error; - le16_to_cpus(&range_y); + data->max_y = get_unaligned_le16(&range_y); + /* read orientation config */ error = __mxt_read_reg(client, object->start_address + MXT_T100_CFG1, 1, &cfg); if (error) return error; + data->xy_switch = cfg & MXT_T100_CFG_SWITCHXY; + + /* allocate aux bytes */ error = __mxt_read_reg(client, object->start_address + MXT_T100_TCHAUX, 1, &tchaux); if (error) return error; - /* Handle default values */ - if (range_x == 0) - range_x = 1023; - - if (range_y == 0) - range_y = 1023; - - if (cfg & MXT_T100_CFG_SWITCHXY) { - data->max_x = range_y; - data->max_y = range_x; - } else { - data->max_x = range_x; - data->max_y = range_y; - } - - /* allocate aux bytes */ aux = 6; if (tchaux & MXT_T100_TCHAUX_VECT) @@ -1767,9 +1741,6 @@ static int mxt_read_t100_config(struct mxt_data *data) "T100 aux mappings vect:%u ampl:%u area:%u\n", data->t100_aux_vect, data->t100_aux_ampl, data->t100_aux_area); - dev_info(&client->dev, - "T100 Touchscreen size X%uY%u\n", data->max_x, data->max_y); - return 0; } @@ -1828,6 +1799,19 @@ static int mxt_initialize_input_device(struct mxt_data *data) return -EINVAL; } + /* Handle default values and orientation switch */ + if (data->max_x == 0) + data->max_x = 1023; + + if (data->max_y == 0) + data->max_y = 1023; + + if (data->xy_switch) + swap(data->max_x, data->max_y); + + dev_info(dev, "Touchscreen size X%uY%u\n", data->max_x, data->max_y); + + /* Register input device */ input_dev = input_allocate_device(); if (!input_dev) { dev_err(dev, "Failed to allocate memory\n"); diff --git a/drivers/input/touchscreen/bcm_iproc_tsc.c b/drivers/input/touchscreen/bcm_iproc_tsc.c index ae460a5..4d11b27 100644 --- a/drivers/input/touchscreen/bcm_iproc_tsc.c +++ b/drivers/input/touchscreen/bcm_iproc_tsc.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #define IPROC_TS_NAME "iproc-ts" @@ -88,7 +90,11 @@ #define TS_WIRE_MODE_BIT BIT(1) #define dbg_reg(dev, priv, reg) \ - dev_dbg(dev, "%20s= 0x%08x\n", #reg, readl((priv)->regs + reg)) +do { \ + u32 val; \ + regmap_read(priv->regmap, reg, &val); \ + dev_dbg(dev, "%20s= 0x%08x\n", #reg, val); \ +} while (0) struct tsc_param { /* Each step is 1024 us. Valid 1-256 */ @@ -141,7 +147,7 @@ struct iproc_ts_priv { struct platform_device *pdev; struct input_dev *idev; - void __iomem *regs; + struct regmap *regmap; struct clk *tsc_clk; int pen_status; @@ -196,22 +202,22 @@ static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) int i; bool needs_sync = false; - intr_status = readl(priv->regs + INTERRUPT_STATUS); + regmap_read(priv->regmap, INTERRUPT_STATUS, &intr_status); intr_status &= TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; if (intr_status == 0) return IRQ_NONE; /* Clear all interrupt status bits, write-1-clear */ - writel(intr_status, priv->regs + INTERRUPT_STATUS); - + regmap_write(priv->regmap, INTERRUPT_STATUS, intr_status); /* Pen up/down */ if (intr_status & TS_PEN_INTR_MASK) { - if (readl(priv->regs + CONTROLLER_STATUS) & TS_PEN_DOWN) + regmap_read(priv->regmap, CONTROLLER_STATUS, &priv->pen_status); + if (priv->pen_status & TS_PEN_DOWN) priv->pen_status = PEN_DOWN_STATUS; else priv->pen_status = PEN_UP_STATUS; - input_report_key(priv->idev, BTN_TOUCH, priv->pen_status); + input_report_key(priv->idev, BTN_TOUCH, priv->pen_status); needs_sync = true; dev_dbg(&priv->pdev->dev, @@ -221,7 +227,7 @@ static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) /* coordinates in FIFO exceed the theshold */ if (intr_status & TS_FIFO_INTR_MASK) { for (i = 0; i < priv->cfg_params.fifo_threshold; i++) { - raw_coordinate = readl(priv->regs + FIFO_DATA); + regmap_read(priv->regmap, FIFO_DATA, &raw_coordinate); if (raw_coordinate == INVALID_COORD) continue; @@ -239,7 +245,7 @@ static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) x = (x >> 4) & 0x0FFF; y = (y >> 4) & 0x0FFF; - /* adjust x y according to lcd tsc mount angle */ + /* Adjust x y according to LCD tsc mount angle. */ if (priv->cfg_params.invert_x) x = priv->cfg_params.max_x - x; @@ -262,9 +268,10 @@ static irqreturn_t iproc_touchscreen_interrupt(int irq, void *data) static int iproc_ts_start(struct input_dev *idev) { - struct iproc_ts_priv *priv = input_get_drvdata(idev); u32 val; + u32 mask; int error; + struct iproc_ts_priv *priv = input_get_drvdata(idev); /* Enable clock */ error = clk_prepare_enable(priv->tsc_clk); @@ -279,9 +286,10 @@ static int iproc_ts_start(struct input_dev *idev) * FIFO reaches the int_th value, and pen event(up/down) */ val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; - writel(val, priv->regs + INTERRUPT_MASK); + regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, val); - writel(priv->cfg_params.fifo_threshold, priv->regs + INTERRUPT_THRES); + val = priv->cfg_params.fifo_threshold; + regmap_write(priv->regmap, INTERRUPT_THRES, val); /* Initialize control reg1 */ val = 0; @@ -289,26 +297,23 @@ static int iproc_ts_start(struct input_dev *idev) val |= priv->cfg_params.debounce_timeout << DEBOUNCE_TIMEOUT_SHIFT; val |= priv->cfg_params.settling_timeout << SETTLING_TIMEOUT_SHIFT; val |= priv->cfg_params.touch_timeout << TOUCH_TIMEOUT_SHIFT; - writel(val, priv->regs + REGCTL1); + regmap_write(priv->regmap, REGCTL1, val); /* Try to clear all interrupt status */ - val = readl(priv->regs + INTERRUPT_STATUS); - val |= TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; - writel(val, priv->regs + INTERRUPT_STATUS); + val = TS_FIFO_INTR_MASK | TS_PEN_INTR_MASK; + regmap_update_bits(priv->regmap, INTERRUPT_STATUS, val, val); /* Initialize control reg2 */ - val = readl(priv->regs + REGCTL2); - val |= TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; - - val &= ~TS_CONTROLLER_AVGDATA_MASK; + val = TS_CONTROLLER_EN_BIT | TS_WIRE_MODE_BIT; val |= priv->cfg_params.average_data << TS_CONTROLLER_AVGDATA_SHIFT; - val &= ~(TS_CONTROLLER_PWR_LDO | /* PWR up LDO */ + mask = (TS_CONTROLLER_AVGDATA_MASK); + mask |= (TS_CONTROLLER_PWR_LDO | /* PWR up LDO */ TS_CONTROLLER_PWR_ADC | /* PWR up ADC */ TS_CONTROLLER_PWR_BGP | /* PWR up BGP */ TS_CONTROLLER_PWR_TS); /* PWR up TS */ - - writel(val, priv->regs + REGCTL2); + mask |= val; + regmap_update_bits(priv->regmap, REGCTL2, mask, val); ts_reg_dump(priv); @@ -320,12 +325,17 @@ static void iproc_ts_stop(struct input_dev *dev) u32 val; struct iproc_ts_priv *priv = input_get_drvdata(dev); - writel(0, priv->regs + INTERRUPT_MASK); /* Disable all interrupts */ + /* + * Disable FIFO int_th and pen event(up/down)Interrupts only + * as the interrupt mask register is shared between ADC, TS and + * flextimer. + */ + val = TS_PEN_INTR_MASK | TS_FIFO_INTR_MASK; + regmap_update_bits(priv->regmap, INTERRUPT_MASK, val, 0); /* Only power down touch screen controller */ - val = readl(priv->regs + REGCTL2); - val |= TS_CONTROLLER_PWR_TS; - writel(val, priv->regs + REGCTL2); + val = TS_CONTROLLER_PWR_TS; + regmap_update_bits(priv->regmap, REGCTL2, val, val); clk_disable(priv->tsc_clk); } @@ -414,7 +424,6 @@ static int iproc_ts_probe(struct platform_device *pdev) { struct iproc_ts_priv *priv; struct input_dev *idev; - struct resource *res; int irq; int error; @@ -422,12 +431,12 @@ static int iproc_ts_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - /* touchscreen controller memory mapped regs */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->regs)) { - error = PTR_ERR(priv->regs); - dev_err(&pdev->dev, "unable to map I/O memory: %d\n", error); + /* touchscreen controller memory mapped regs via syscon*/ + priv->regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "ts_syscon"); + if (IS_ERR(priv->regmap)) { + error = PTR_ERR(priv->regmap); + dev_err(&pdev->dev, "unable to map I/O memory:%d\n", error); return error; } diff --git a/drivers/input/touchscreen/chipone_icn8318.c b/drivers/input/touchscreen/chipone_icn8318.c index 22a6fea..0bf1406 100644 --- a/drivers/input/touchscreen/chipone_icn8318.c +++ b/drivers/input/touchscreen/chipone_icn8318.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -52,11 +53,7 @@ struct icn8318_data { struct i2c_client *client; struct input_dev *input; struct gpio_desc *wake_gpio; - u32 max_x; - u32 max_y; - bool invert_x; - bool invert_y; - bool swap_x_y; + struct touchscreen_properties prop; }; static int icn8318_read_touch_data(struct i2c_client *client, @@ -91,7 +88,7 @@ static irqreturn_t icn8318_irq(int irq, void *dev_id) struct icn8318_data *data = dev_id; struct device *dev = &data->client->dev; struct icn8318_touch_data touch_data; - int i, ret, x, y; + int i, ret; ret = icn8318_read_touch_data(data->client, &touch_data); if (ret < 0) { @@ -124,22 +121,9 @@ static irqreturn_t icn8318_irq(int irq, void *dev_id) if (!act) continue; - x = be16_to_cpu(touch->x); - y = be16_to_cpu(touch->y); - - if (data->invert_x) - x = data->max_x - x; - - if (data->invert_y) - y = data->max_y - y; - - if (!data->swap_x_y) { - input_event(data->input, EV_ABS, ABS_MT_POSITION_X, x); - input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, y); - } else { - input_event(data->input, EV_ABS, ABS_MT_POSITION_X, y); - input_event(data->input, EV_ABS, ABS_MT_POSITION_Y, x); - } + touchscreen_report_pos(data->input, &data->prop, + be16_to_cpu(touch->x), + be16_to_cpu(touch->y), true); } input_mt_sync_frame(data->input); @@ -200,10 +184,8 @@ static int icn8318_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; - struct device_node *np = dev->of_node; struct icn8318_data *data; struct input_dev *input; - u32 fuzz_x = 0, fuzz_y = 0; int error; if (!client->irq) { @@ -223,19 +205,6 @@ static int icn8318_probe(struct i2c_client *client, return error; } - if (of_property_read_u32(np, "touchscreen-size-x", &data->max_x) || - of_property_read_u32(np, "touchscreen-size-y", &data->max_y)) { - dev_err(dev, "Error touchscreen-size-x and/or -y missing\n"); - return -EINVAL; - } - - /* Optional */ - of_property_read_u32(np, "touchscreen-fuzz-x", &fuzz_x); - of_property_read_u32(np, "touchscreen-fuzz-y", &fuzz_y); - data->invert_x = of_property_read_bool(np, "touchscreen-inverted-x"); - data->invert_y = of_property_read_bool(np, "touchscreen-inverted-y"); - data->swap_x_y = of_property_read_bool(np, "touchscreen-swapped-x-y"); - input = devm_input_allocate_device(dev); if (!input) return -ENOMEM; @@ -246,16 +215,14 @@ static int icn8318_probe(struct i2c_client *client, input->close = icn8318_stop; input->dev.parent = dev; - if (!data->swap_x_y) { - input_set_abs_params(input, ABS_MT_POSITION_X, 0, - data->max_x, fuzz_x, 0); - input_set_abs_params(input, ABS_MT_POSITION_Y, 0, - data->max_y, fuzz_y, 0); - } else { - input_set_abs_params(input, ABS_MT_POSITION_X, 0, - data->max_y, fuzz_y, 0); - input_set_abs_params(input, ABS_MT_POSITION_Y, 0, - data->max_x, fuzz_x, 0); + input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); + + touchscreen_parse_properties(input, true, &data->prop); + if (!input_abs_get_max(input, ABS_MT_POSITION_X) || + !input_abs_get_max(input, ABS_MT_POSITION_Y)) { + dev_err(dev, "Error touchscreen-size-x and/or -y missing\n"); + return -EINVAL; } error = input_mt_init_slots(input, ICN8318_MAX_TOUCHES, diff --git a/drivers/input/touchscreen/colibri-vf50-ts.c b/drivers/input/touchscreen/colibri-vf50-ts.c index 5d4903a..69828d0 100644 --- a/drivers/input/touchscreen/colibri-vf50-ts.c +++ b/drivers/input/touchscreen/colibri-vf50-ts.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/input/touchscreen/cyttsp4_core.c b/drivers/input/touchscreen/cyttsp4_core.c index 5ed3105..44deca8 100644 --- a/drivers/input/touchscreen/cyttsp4_core.c +++ b/drivers/input/touchscreen/cyttsp4_core.c @@ -1499,7 +1499,7 @@ static int cyttsp4_core_sleep_(struct cyttsp4 *cd) if (IS_BOOTLOADER(mode[0], mode[1])) { mutex_unlock(&cd->system_lock); - dev_err(cd->dev, "%s: Device in BOOTLADER mode.\n", __func__); + dev_err(cd->dev, "%s: Device in BOOTLOADER mode.\n", __func__); rc = -EINVAL; goto error; } diff --git a/drivers/input/touchscreen/cyttsp_core.c b/drivers/input/touchscreen/cyttsp_core.c index 5b74e8b..79381cc 100644 --- a/drivers/input/touchscreen/cyttsp_core.c +++ b/drivers/input/touchscreen/cyttsp_core.c @@ -30,9 +30,12 @@ #include #include #include +#include #include #include #include +#include +#include #include "cyttsp_core.h" @@ -57,6 +60,7 @@ #define CY_DELAY_DFLT 20 /* ms */ #define CY_DELAY_MAX 500 #define CY_ACT_DIST_DFLT 0xF8 +#define CY_ACT_DIST_MASK 0x0F #define CY_HNDSHK_BIT 0x80 /* device mode bits */ #define CY_OPERATE_MODE 0x00 @@ -120,7 +124,7 @@ static int ttsp_send_command(struct cyttsp *ts, u8 cmd) static int cyttsp_handshake(struct cyttsp *ts) { - if (ts->pdata->use_hndshk) + if (ts->use_hndshk) return ttsp_send_command(ts, ts->xy_data.hst_mode ^ CY_HNDSHK_BIT); @@ -142,9 +146,9 @@ static int cyttsp_exit_bl_mode(struct cyttsp *ts) u8 bl_cmd[sizeof(bl_command)]; memcpy(bl_cmd, bl_command, sizeof(bl_command)); - if (ts->pdata->bl_keys) + if (ts->bl_keys) memcpy(&bl_cmd[sizeof(bl_command) - CY_NUM_BL_KEYS], - ts->pdata->bl_keys, CY_NUM_BL_KEYS); + ts->bl_keys, CY_NUM_BL_KEYS); error = ttsp_write_block_data(ts, CY_REG_BASE, sizeof(bl_cmd), bl_cmd); @@ -217,14 +221,14 @@ static int cyttsp_set_sysinfo_regs(struct cyttsp *ts) { int retval = 0; - if (ts->pdata->act_intrvl != CY_ACT_INTRVL_DFLT || - ts->pdata->tch_tmout != CY_TCH_TMOUT_DFLT || - ts->pdata->lp_intrvl != CY_LP_INTRVL_DFLT) { + if (ts->act_intrvl != CY_ACT_INTRVL_DFLT || + ts->tch_tmout != CY_TCH_TMOUT_DFLT || + ts->lp_intrvl != CY_LP_INTRVL_DFLT) { u8 intrvl_ray[] = { - ts->pdata->act_intrvl, - ts->pdata->tch_tmout, - ts->pdata->lp_intrvl + ts->act_intrvl, + ts->tch_tmout, + ts->lp_intrvl }; /* set intrvl registers */ @@ -236,6 +240,16 @@ static int cyttsp_set_sysinfo_regs(struct cyttsp *ts) return retval; } +static void cyttsp_hard_reset(struct cyttsp *ts) +{ + if (ts->reset_gpio) { + gpiod_set_value_cansleep(ts->reset_gpio, 1); + msleep(CY_DELAY_DFLT); + gpiod_set_value_cansleep(ts->reset_gpio, 0); + msleep(CY_DELAY_DFLT); + } +} + static int cyttsp_soft_reset(struct cyttsp *ts) { unsigned long timeout; @@ -263,7 +277,7 @@ out: static int cyttsp_act_dist_setup(struct cyttsp *ts) { - u8 act_dist_setup = ts->pdata->act_dist; + u8 act_dist_setup = ts->act_dist; /* Init gesture; active distance setup */ return ttsp_write_block_data(ts, CY_REG_ACT_DIST, @@ -528,45 +542,110 @@ static void cyttsp_close(struct input_dev *dev) cyttsp_disable(ts); } +static int cyttsp_parse_properties(struct cyttsp *ts) +{ + struct device *dev = ts->dev; + u32 dt_value; + int ret; + + ts->bl_keys = devm_kzalloc(dev, CY_NUM_BL_KEYS, GFP_KERNEL); + if (!ts->bl_keys) + return -ENOMEM; + + /* Set some default values */ + ts->use_hndshk = false; + ts->act_dist = CY_ACT_DIST_DFLT; + ts->act_intrvl = CY_ACT_INTRVL_DFLT; + ts->tch_tmout = CY_TCH_TMOUT_DFLT; + ts->lp_intrvl = CY_LP_INTRVL_DFLT; + + ret = device_property_read_u8_array(dev, "bootloader-key", + ts->bl_keys, CY_NUM_BL_KEYS); + if (ret) { + dev_err(dev, + "bootloader-key property could not be retrieved\n"); + return ret; + } + + ts->use_hndshk = device_property_present(dev, "use-handshake"); + + if (!device_property_read_u32(dev, "active-distance", &dt_value)) { + if (dt_value > 15) { + dev_err(dev, "active-distance (%u) must be [0-15]\n", + dt_value); + return -EINVAL; + } + ts->act_dist &= ~CY_ACT_DIST_MASK; + ts->act_dist |= dt_value; + } + + if (!device_property_read_u32(dev, "active-interval-ms", &dt_value)) { + if (dt_value > 255) { + dev_err(dev, "active-interval-ms (%u) must be [0-255]\n", + dt_value); + return -EINVAL; + } + ts->act_intrvl = dt_value; + } + + if (!device_property_read_u32(dev, "lowpower-interval-ms", &dt_value)) { + if (dt_value > 2550) { + dev_err(dev, "lowpower-interval-ms (%u) must be [0-2550]\n", + dt_value); + return -EINVAL; + } + /* Register value is expressed in 0.01s / bit */ + ts->lp_intrvl = dt_value / 10; + } + + if (!device_property_read_u32(dev, "touch-timeout-ms", &dt_value)) { + if (dt_value > 2550) { + dev_err(dev, "touch-timeout-ms (%u) must be [0-2550]\n", + dt_value); + return -EINVAL; + } + /* Register value is expressed in 0.01s / bit */ + ts->tch_tmout = dt_value / 10; + } + + return 0; +} + struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops, struct device *dev, int irq, size_t xfer_buf_size) { - const struct cyttsp_platform_data *pdata = dev_get_platdata(dev); struct cyttsp *ts; struct input_dev *input_dev; int error; - if (!pdata || !pdata->name || irq <= 0) { - error = -EINVAL; - goto err_out; - } + ts = devm_kzalloc(dev, sizeof(*ts) + xfer_buf_size, GFP_KERNEL); + if (!ts) + return ERR_PTR(-ENOMEM); - ts = kzalloc(sizeof(*ts) + xfer_buf_size, GFP_KERNEL); - input_dev = input_allocate_device(); - if (!ts || !input_dev) { - error = -ENOMEM; - goto err_free_mem; - } + input_dev = devm_input_allocate_device(dev); + if (!input_dev) + return ERR_PTR(-ENOMEM); ts->dev = dev; ts->input = input_dev; - ts->pdata = dev_get_platdata(dev); ts->bus_ops = bus_ops; ts->irq = irq; + ts->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) { + error = PTR_ERR(ts->reset_gpio); + dev_err(dev, "Failed to request reset gpio, error %d\n", error); + return ERR_PTR(error); + } + + error = cyttsp_parse_properties(ts); + if (error) + return ERR_PTR(error); + init_completion(&ts->bl_ready); snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(dev)); - if (pdata->init) { - error = pdata->init(); - if (error) { - dev_err(ts->dev, "platform init failed, err: %d\n", - error); - goto err_free_mem; - } - } - - input_dev->name = pdata->name; + input_dev->name = "Cypress TTSP TouchScreen"; input_dev->phys = ts->phys; input_dev->id.bustype = bus_ops->bustype; input_dev->dev.parent = ts->dev; @@ -576,63 +655,44 @@ struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops, input_set_drvdata(input_dev, ts); - __set_bit(EV_ABS, input_dev->evbit); - input_set_abs_params(input_dev, ABS_MT_POSITION_X, - 0, pdata->maxx, 0, 0); - input_set_abs_params(input_dev, ABS_MT_POSITION_Y, - 0, pdata->maxy, 0, 0); - input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, - 0, CY_MAXZ, 0, 0); + input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_X); + input_set_capability(input_dev, EV_ABS, ABS_MT_POSITION_Y); + touchscreen_parse_properties(input_dev, true, NULL); - input_mt_init_slots(input_dev, CY_MAX_ID, 0); + error = input_mt_init_slots(input_dev, CY_MAX_ID, 0); + if (error) { + dev_err(dev, "Unable to init MT slots.\n"); + return ERR_PTR(error); + } - error = request_threaded_irq(ts->irq, NULL, cyttsp_irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - pdata->name, ts); + error = devm_request_threaded_irq(dev, ts->irq, NULL, cyttsp_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cyttsp", ts); if (error) { dev_err(ts->dev, "failed to request IRQ %d, err: %d\n", ts->irq, error); - goto err_platform_exit; + return ERR_PTR(error); } disable_irq(ts->irq); + cyttsp_hard_reset(ts); + error = cyttsp_power_on(ts); if (error) - goto err_free_irq; + return ERR_PTR(error); error = input_register_device(input_dev); if (error) { dev_err(ts->dev, "failed to register input device: %d\n", error); - goto err_free_irq; + return ERR_PTR(error); } return ts; - -err_free_irq: - free_irq(ts->irq, ts); -err_platform_exit: - if (pdata->exit) - pdata->exit(); -err_free_mem: - input_free_device(input_dev); - kfree(ts); -err_out: - return ERR_PTR(error); } EXPORT_SYMBOL_GPL(cyttsp_probe); -void cyttsp_remove(struct cyttsp *ts) -{ - free_irq(ts->irq, ts); - input_unregister_device(ts->input); - if (ts->pdata->exit) - ts->pdata->exit(); - kfree(ts); -} -EXPORT_SYMBOL_GPL(cyttsp_remove); - MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Cypress TrueTouch(R) Standard touchscreen driver core"); MODULE_AUTHOR("Cypress"); diff --git a/drivers/input/touchscreen/cyttsp_core.h b/drivers/input/touchscreen/cyttsp_core.h index 0707411..7835e2b 100644 --- a/drivers/input/touchscreen/cyttsp_core.h +++ b/drivers/input/touchscreen/cyttsp_core.h @@ -129,7 +129,6 @@ struct cyttsp { int irq; struct input_dev *input; char phys[32]; - const struct cyttsp_platform_data *pdata; const struct cyttsp_bus_ops *bus_ops; struct cyttsp_bootloader_data bl_data; struct cyttsp_sysinfo_data sysinfo_data; @@ -138,12 +137,19 @@ struct cyttsp { enum cyttsp_state state; bool suspended; + struct gpio_desc *reset_gpio; + bool use_hndshk; + u8 act_dist; + u8 act_intrvl; + u8 tch_tmout; + u8 lp_intrvl; + u8 *bl_keys; + u8 xfer_buf[] ____cacheline_aligned; }; struct cyttsp *cyttsp_probe(const struct cyttsp_bus_ops *bus_ops, struct device *dev, int irq, size_t xfer_buf_size); -void cyttsp_remove(struct cyttsp *ts); int cyttsp_i2c_write_block_data(struct device *dev, u8 *xfer_buf, u16 addr, u8 length, const void *values); diff --git a/drivers/input/touchscreen/cyttsp_i2c.c b/drivers/input/touchscreen/cyttsp_i2c.c index eee51b3..1edfdba 100644 --- a/drivers/input/touchscreen/cyttsp_i2c.c +++ b/drivers/input/touchscreen/cyttsp_i2c.c @@ -56,15 +56,6 @@ static int cyttsp_i2c_probe(struct i2c_client *client, return 0; } -static int cyttsp_i2c_remove(struct i2c_client *client) -{ - struct cyttsp *ts = i2c_get_clientdata(client); - - cyttsp_remove(ts); - - return 0; -} - static const struct i2c_device_id cyttsp_i2c_id[] = { { CY_I2C_NAME, 0 }, { } @@ -77,7 +68,6 @@ static struct i2c_driver cyttsp_i2c_driver = { .pm = &cyttsp_pm_ops, }, .probe = cyttsp_i2c_probe, - .remove = cyttsp_i2c_remove, .id_table = cyttsp_i2c_id, }; diff --git a/drivers/input/touchscreen/cyttsp_spi.c b/drivers/input/touchscreen/cyttsp_spi.c index bbeeb24..3c9d18b 100644 --- a/drivers/input/touchscreen/cyttsp_spi.c +++ b/drivers/input/touchscreen/cyttsp_spi.c @@ -170,22 +170,12 @@ static int cyttsp_spi_probe(struct spi_device *spi) return 0; } -static int cyttsp_spi_remove(struct spi_device *spi) -{ - struct cyttsp *ts = spi_get_drvdata(spi); - - cyttsp_remove(ts); - - return 0; -} - static struct spi_driver cyttsp_spi_driver = { .driver = { .name = CY_SPI_NAME, .pm = &cyttsp_pm_ops, }, .probe = cyttsp_spi_probe, - .remove = cyttsp_spi_remove, }; module_spi_driver(cyttsp_spi_driver); diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c index 0b0f8c1..703e295 100644 --- a/drivers/input/touchscreen/edt-ft5x06.c +++ b/drivers/input/touchscreen/edt-ft5x06.c @@ -86,6 +86,7 @@ struct edt_reg_addr { struct edt_ft5x06_ts_data { struct i2c_client *client; struct input_dev *input; + struct touchscreen_properties prop; u16 num_x; u16 num_y; @@ -246,8 +247,8 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id) if (!down) continue; - input_report_abs(tsdata->input, ABS_MT_POSITION_X, x); - input_report_abs(tsdata->input, ABS_MT_POSITION_Y, y); + touchscreen_report_pos(tsdata->input, &tsdata->prop, x, y, + true); } input_mt_report_pointer_emulation(tsdata->input, true); @@ -822,16 +823,22 @@ static void edt_ft5x06_ts_get_defaults(struct device *dev, int error; error = device_property_read_u32(dev, "threshold", &val); - if (!error) - reg_addr->reg_threshold = val; + if (!error) { + edt_ft5x06_register_write(tsdata, reg_addr->reg_threshold, val); + tsdata->threshold = val; + } error = device_property_read_u32(dev, "gain", &val); - if (!error) - reg_addr->reg_gain = val; + if (!error) { + edt_ft5x06_register_write(tsdata, reg_addr->reg_gain, val); + tsdata->gain = val; + } error = device_property_read_u32(dev, "offset", &val); - if (!error) - reg_addr->reg_offset = val; + if (!error) { + edt_ft5x06_register_write(tsdata, reg_addr->reg_offset, val); + tsdata->offset = val; + } } static void @@ -966,7 +973,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client, input_set_abs_params(input, ABS_MT_POSITION_Y, 0, tsdata->num_y * 64 - 1, 0, 0); - touchscreen_parse_properties(input, true); + touchscreen_parse_properties(input, true, &tsdata->prop); error = input_mt_init_slots(input, tsdata->max_support_points, INPUT_MT_DIRECT); diff --git b/drivers/input/touchscreen/egalax_ts_serial.c b/drivers/input/touchscreen/egalax_ts_serial.c new file mode 100644 index 0000000..657bbae --- /dev/null +++ b/drivers/input/touchscreen/egalax_ts_serial.c @@ -0,0 +1,194 @@ +/* + * EETI Egalax serial touchscreen driver + * + * Copyright (c) 2015 Zoltán Böszörményi + * + * based on the + * + * Hampshire serial touchscreen driver (Copyright (c) 2010 Adam Bennett) + */ + +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#define DRIVER_DESC "EETI Egalax serial touchscreen driver" + +/* + * Definitions & global arrays. + */ + +#define EGALAX_FORMAT_MAX_LENGTH 6 +#define EGALAX_FORMAT_START_BIT BIT(7) +#define EGALAX_FORMAT_PRESSURE_BIT BIT(6) +#define EGALAX_FORMAT_TOUCH_BIT BIT(0) +#define EGALAX_FORMAT_RESOLUTION_MASK 0x06 + +#define EGALAX_MIN_XC 0 +#define EGALAX_MAX_XC 0x4000 +#define EGALAX_MIN_YC 0 +#define EGALAX_MAX_YC 0x4000 + +/* + * Per-touchscreen data. + */ +struct egalax { + struct input_dev *input; + struct serio *serio; + int idx; + u8 data[EGALAX_FORMAT_MAX_LENGTH]; + char phys[32]; +}; + +static void egalax_process_data(struct egalax *egalax) +{ + struct input_dev *dev = egalax->input; + u8 *data = egalax->data; + u16 x, y; + u8 shift; + u8 mask; + + shift = 3 - ((data[0] & EGALAX_FORMAT_RESOLUTION_MASK) >> 1); + mask = 0xff >> (shift + 1); + + x = (((u16)(data[1] & mask) << 7) | (data[2] & 0x7f)) << shift; + y = (((u16)(data[3] & mask) << 7) | (data[4] & 0x7f)) << shift; + + input_report_key(dev, BTN_TOUCH, data[0] & EGALAX_FORMAT_TOUCH_BIT); + input_report_abs(dev, ABS_X, x); + input_report_abs(dev, ABS_Y, y); + input_sync(dev); +} + +static irqreturn_t egalax_interrupt(struct serio *serio, + unsigned char data, unsigned int flags) +{ + struct egalax *egalax = serio_get_drvdata(serio); + int pkt_len; + + egalax->data[egalax->idx++] = data; + + if (likely(egalax->data[0] & EGALAX_FORMAT_START_BIT)) { + pkt_len = egalax->data[0] & EGALAX_FORMAT_PRESSURE_BIT ? 6 : 5; + if (pkt_len == egalax->idx) { + egalax_process_data(egalax); + egalax->idx = 0; + } + } else { + dev_dbg(&serio->dev, "unknown/unsynchronized data: %x\n", + egalax->data[0]); + egalax->idx = 0; + } + + return IRQ_HANDLED; +} + +/* + * egalax_connect() is the routine that is called when someone adds a + * new serio device that supports egalax protocol and registers it as + * an input device. This is usually accomplished using inputattach. + */ +static int egalax_connect(struct serio *serio, struct serio_driver *drv) +{ + struct egalax *egalax; + struct input_dev *input_dev; + int error; + + egalax = kzalloc(sizeof(struct egalax), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!egalax || !input_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + egalax->serio = serio; + egalax->input = input_dev; + snprintf(egalax->phys, sizeof(egalax->phys), + "%s/input0", serio->phys); + + input_dev->name = "EETI eGalaxTouch Serial TouchScreen"; + input_dev->phys = egalax->phys; + input_dev->id.bustype = BUS_RS232; + input_dev->id.vendor = SERIO_EGALAX; + input_dev->id.product = 0; + input_dev->id.version = 0x0001; + input_dev->dev.parent = &serio->dev; + + input_set_capability(input_dev, EV_KEY, BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, + EGALAX_MIN_XC, EGALAX_MAX_XC, 0, 0); + input_set_abs_params(input_dev, ABS_Y, + EGALAX_MIN_YC, EGALAX_MAX_YC, 0, 0); + + serio_set_drvdata(serio, egalax); + + error = serio_open(serio, drv); + if (error) + goto err_reset_drvdata; + + error = input_register_device(input_dev); + if (error) + goto err_close_serio; + + return 0; + +err_close_serio: + serio_close(serio); +err_reset_drvdata: + serio_set_drvdata(serio, NULL); +err_free_mem: + input_free_device(input_dev); + kfree(egalax); + return error; +} + +static void egalax_disconnect(struct serio *serio) +{ + struct egalax *egalax = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + input_unregister_device(egalax->input); + kfree(egalax); +} + +/* + * The serio driver structure. + */ + +static const struct serio_device_id egalax_serio_ids[] = { + { + .type = SERIO_RS232, + .proto = SERIO_EGALAX, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, egalax_serio_ids); + +static struct serio_driver egalax_drv = { + .driver = { + .name = "egalax", + }, + .description = DRIVER_DESC, + .id_table = egalax_serio_ids, + .interrupt = egalax_interrupt, + .connect = egalax_connect, + .disconnect = egalax_disconnect, +}; +module_serio_driver(egalax_drv); + +MODULE_AUTHOR("Zoltán Böszörményi "); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git b/drivers/input/touchscreen/fsl-imx25-tcq.c b/drivers/input/touchscreen/fsl-imx25-tcq.c new file mode 100644 index 0000000..fe9877a --- /dev/null +++ b/drivers/input/touchscreen/fsl-imx25-tcq.c @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2014-2015 Pengutronix, Markus Pargmann + * + * 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. + * + * Based on driver from 2011: + * Juergen Beisert, Pengutronix + * + * This is the driver for the imx25 TCQ (Touchscreen Conversion Queue) + * connected to the imx25 ADC. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char mx25_tcq_name[] = "mx25-tcq"; + +enum mx25_tcq_mode { + MX25_TS_4WIRE, +}; + +struct mx25_tcq_priv { + struct regmap *regs; + struct regmap *core_regs; + struct input_dev *idev; + enum mx25_tcq_mode mode; + unsigned int pen_threshold; + unsigned int sample_count; + unsigned int expected_samples; + unsigned int pen_debounce; + unsigned int settling_time; + struct clk *clk; + int irq; + struct device *dev; +}; + +static struct regmap_config mx25_tcq_regconfig = { + .fast_io = true, + .max_register = 0x5c, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static const struct of_device_id mx25_tcq_ids[] = { + { .compatible = "fsl,imx25-tcq", }, + { /* Sentinel */ } +}; + +#define TSC_4WIRE_PRE_INDEX 0 +#define TSC_4WIRE_X_INDEX 1 +#define TSC_4WIRE_Y_INDEX 2 +#define TSC_4WIRE_POST_INDEX 3 +#define TSC_4WIRE_LEAVE 4 + +#define MX25_TSC_DEF_THRESHOLD 80 +#define TSC_MAX_SAMPLES 16 + +#define MX25_TSC_REPEAT_WAIT 14 + +enum mx25_adc_configurations { + MX25_CFG_PRECHARGE = 0, + MX25_CFG_TOUCH_DETECT, + MX25_CFG_X_MEASUREMENT, + MX25_CFG_Y_MEASUREMENT, +}; + +#define MX25_PRECHARGE_VALUE (\ + MX25_ADCQ_CFG_YPLL_OFF | \ + MX25_ADCQ_CFG_XNUR_OFF | \ + MX25_ADCQ_CFG_XPUL_HIGH | \ + MX25_ADCQ_CFG_REFP_INT | \ + MX25_ADCQ_CFG_IN_XP | \ + MX25_ADCQ_CFG_REFN_NGND2 | \ + MX25_ADCQ_CFG_IGS) + +#define MX25_TOUCH_DETECT_VALUE (\ + MX25_ADCQ_CFG_YNLR | \ + MX25_ADCQ_CFG_YPLL_OFF | \ + MX25_ADCQ_CFG_XNUR_OFF | \ + MX25_ADCQ_CFG_XPUL_OFF | \ + MX25_ADCQ_CFG_REFP_INT | \ + MX25_ADCQ_CFG_IN_XP | \ + MX25_ADCQ_CFG_REFN_NGND2 | \ + MX25_ADCQ_CFG_PENIACK) + +static void imx25_setup_queue_cfgs(struct mx25_tcq_priv *priv, + unsigned int settling_cnt) +{ + u32 precharge_cfg = + MX25_PRECHARGE_VALUE | + MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt); + u32 touch_detect_cfg = + MX25_TOUCH_DETECT_VALUE | + MX25_ADCQ_CFG_NOS(1) | + MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt); + + regmap_write(priv->core_regs, MX25_TSC_TICR, precharge_cfg); + + /* PRECHARGE */ + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_PRECHARGE), + precharge_cfg); + + /* TOUCH_DETECT */ + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_TOUCH_DETECT), + touch_detect_cfg); + + /* X Measurement */ + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_X_MEASUREMENT), + MX25_ADCQ_CFG_YPLL_OFF | + MX25_ADCQ_CFG_XNUR_LOW | + MX25_ADCQ_CFG_XPUL_HIGH | + MX25_ADCQ_CFG_REFP_XP | + MX25_ADCQ_CFG_IN_YP | + MX25_ADCQ_CFG_REFN_XN | + MX25_ADCQ_CFG_NOS(priv->sample_count) | + MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt)); + + /* Y Measurement */ + regmap_write(priv->regs, MX25_ADCQ_CFG(MX25_CFG_Y_MEASUREMENT), + MX25_ADCQ_CFG_YNLR | + MX25_ADCQ_CFG_YPLL_HIGH | + MX25_ADCQ_CFG_XNUR_OFF | + MX25_ADCQ_CFG_XPUL_OFF | + MX25_ADCQ_CFG_REFP_YP | + MX25_ADCQ_CFG_IN_XP | + MX25_ADCQ_CFG_REFN_YN | + MX25_ADCQ_CFG_NOS(priv->sample_count) | + MX25_ADCQ_CFG_SETTLING_TIME(settling_cnt)); + + /* Enable the touch detection right now */ + regmap_write(priv->core_regs, MX25_TSC_TICR, touch_detect_cfg | + MX25_ADCQ_CFG_IGS); +} + +static int imx25_setup_queue_4wire(struct mx25_tcq_priv *priv, + unsigned settling_cnt, int *items) +{ + imx25_setup_queue_cfgs(priv, settling_cnt); + + /* Setup the conversion queue */ + regmap_write(priv->regs, MX25_ADCQ_ITEM_7_0, + MX25_ADCQ_ITEM(0, MX25_CFG_PRECHARGE) | + MX25_ADCQ_ITEM(1, MX25_CFG_TOUCH_DETECT) | + MX25_ADCQ_ITEM(2, MX25_CFG_X_MEASUREMENT) | + MX25_ADCQ_ITEM(3, MX25_CFG_Y_MEASUREMENT) | + MX25_ADCQ_ITEM(4, MX25_CFG_PRECHARGE) | + MX25_ADCQ_ITEM(5, MX25_CFG_TOUCH_DETECT)); + + /* + * We measure X/Y with 'sample_count' number of samples and execute a + * touch detection twice, with 1 sample each + */ + priv->expected_samples = priv->sample_count * 2 + 2; + *items = 6; + + return 0; +} + +static void mx25_tcq_disable_touch_irq(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK, + MX25_ADCQ_CR_PDMSK); +} + +static void mx25_tcq_enable_touch_irq(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_PDMSK, 0); +} + +static void mx25_tcq_disable_fifo_irq(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ, + MX25_ADCQ_MR_FDRY_IRQ); +} + +static void mx25_tcq_enable_fifo_irq(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_FDRY_IRQ, 0); +} + +static void mx25_tcq_force_queue_start(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_FQS, + MX25_ADCQ_CR_FQS); +} + +static void mx25_tcq_force_queue_stop(struct mx25_tcq_priv *priv) +{ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_FQS, 0); +} + +static void mx25_tcq_fifo_reset(struct mx25_tcq_priv *priv) +{ + u32 tcqcr; + + regmap_read(priv->regs, MX25_ADCQ_CR, &tcqcr); + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST, + MX25_ADCQ_CR_FRST); + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_FRST, 0); + regmap_write(priv->regs, MX25_ADCQ_CR, tcqcr); +} + +static void mx25_tcq_re_enable_touch_detection(struct mx25_tcq_priv *priv) +{ + /* stop the queue from looping */ + mx25_tcq_force_queue_stop(priv); + + /* for a clean touch detection, preload the X plane */ + regmap_write(priv->core_regs, MX25_TSC_TICR, MX25_PRECHARGE_VALUE); + + /* waste some time now to pre-load the X plate to high voltage */ + mx25_tcq_fifo_reset(priv); + + /* re-enable the detection right now */ + regmap_write(priv->core_regs, MX25_TSC_TICR, + MX25_TOUCH_DETECT_VALUE | MX25_ADCQ_CFG_IGS); + + regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_PD, + MX25_ADCQ_SR_PD); + + /* enable the pen down event to be a source for the interrupt */ + regmap_update_bits(priv->regs, MX25_ADCQ_MR, MX25_ADCQ_MR_PD_IRQ, 0); + + /* lets fire the next IRQ if someone touches the touchscreen */ + mx25_tcq_enable_touch_irq(priv); +} + +static void mx25_tcq_create_event_for_4wire(struct mx25_tcq_priv *priv, + u32 *sample_buf, + unsigned int samples) +{ + unsigned int x_pos = 0; + unsigned int y_pos = 0; + unsigned int touch_pre = 0; + unsigned int touch_post = 0; + unsigned int i; + + for (i = 0; i < samples; i++) { + unsigned int index = MX25_ADCQ_FIFO_ID(sample_buf[i]); + unsigned int val = MX25_ADCQ_FIFO_DATA(sample_buf[i]); + + switch (index) { + case 1: + touch_pre = val; + break; + case 2: + x_pos = val; + break; + case 3: + y_pos = val; + break; + case 5: + touch_post = val; + break; + default: + dev_dbg(priv->dev, "Dropped samples because of invalid index %d\n", + index); + return; + } + } + + if (samples != 0) { + /* + * only if both touch measures are below a threshold, + * the position is valid + */ + if (touch_pre < priv->pen_threshold && + touch_post < priv->pen_threshold) { + /* valid samples, generate a report */ + x_pos /= priv->sample_count; + y_pos /= priv->sample_count; + input_report_abs(priv->idev, ABS_X, x_pos); + input_report_abs(priv->idev, ABS_Y, y_pos); + input_report_key(priv->idev, BTN_TOUCH, 1); + input_sync(priv->idev); + + /* get next sample */ + mx25_tcq_enable_fifo_irq(priv); + } else if (touch_pre >= priv->pen_threshold && + touch_post >= priv->pen_threshold) { + /* + * if both samples are invalid, + * generate a release report + */ + input_report_key(priv->idev, BTN_TOUCH, 0); + input_sync(priv->idev); + mx25_tcq_re_enable_touch_detection(priv); + } else { + /* + * if only one of both touch measurements are + * below the threshold, still some bouncing + * happens. Take additional samples in this + * case to be sure + */ + mx25_tcq_enable_fifo_irq(priv); + } + } +} + +static irqreturn_t mx25_tcq_irq_thread(int irq, void *dev_id) +{ + struct mx25_tcq_priv *priv = dev_id; + u32 sample_buf[TSC_MAX_SAMPLES]; + unsigned int samples; + u32 stats; + unsigned int i; + + /* + * Check how many samples are available. We always have to read exactly + * sample_count samples from the fifo, or a multiple of sample_count. + * Otherwise we mixup samples into different touch events. + */ + regmap_read(priv->regs, MX25_ADCQ_SR, &stats); + samples = MX25_ADCQ_SR_FDN(stats); + samples -= samples % priv->sample_count; + + if (!samples) + return IRQ_HANDLED; + + for (i = 0; i != samples; ++i) + regmap_read(priv->regs, MX25_ADCQ_FIFO, &sample_buf[i]); + + mx25_tcq_create_event_for_4wire(priv, sample_buf, samples); + + return IRQ_HANDLED; +} + +static irqreturn_t mx25_tcq_irq(int irq, void *dev_id) +{ + struct mx25_tcq_priv *priv = dev_id; + u32 stat; + int ret = IRQ_HANDLED; + + regmap_read(priv->regs, MX25_ADCQ_SR, &stat); + + if (stat & (MX25_ADCQ_SR_FRR | MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR)) + mx25_tcq_re_enable_touch_detection(priv); + + if (stat & MX25_ADCQ_SR_PD) { + mx25_tcq_disable_touch_irq(priv); + mx25_tcq_force_queue_start(priv); + mx25_tcq_enable_fifo_irq(priv); + } + + if (stat & MX25_ADCQ_SR_FDRY) { + mx25_tcq_disable_fifo_irq(priv); + ret = IRQ_WAKE_THREAD; + } + + regmap_update_bits(priv->regs, MX25_ADCQ_SR, MX25_ADCQ_SR_FRR | + MX25_ADCQ_SR_FUR | MX25_ADCQ_SR_FOR | + MX25_ADCQ_SR_PD, + MX25_ADCQ_SR_FRR | MX25_ADCQ_SR_FUR | + MX25_ADCQ_SR_FOR | MX25_ADCQ_SR_PD); + + return ret; +} + +/* configure the state machine for a 4-wire touchscreen */ +static int mx25_tcq_init(struct mx25_tcq_priv *priv) +{ + u32 tgcr; + unsigned int ipg_div; + unsigned int adc_period; + unsigned int debounce_cnt; + unsigned int settling_cnt; + int itemct; + int error; + + regmap_read(priv->core_regs, MX25_TSC_TGCR, &tgcr); + ipg_div = max_t(unsigned int, 4, MX25_TGCR_GET_ADCCLK(tgcr)); + adc_period = USEC_PER_SEC * ipg_div * 2 + 2; + adc_period /= clk_get_rate(priv->clk) / 1000 + 1; + debounce_cnt = DIV_ROUND_UP(priv->pen_debounce, adc_period * 8) - 1; + settling_cnt = DIV_ROUND_UP(priv->settling_time, adc_period * 8) - 1; + + /* Reset */ + regmap_write(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_QRST | MX25_ADCQ_CR_FRST); + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_QRST | MX25_ADCQ_CR_FRST, 0); + + /* up to 128 * 8 ADC clocks are possible */ + if (debounce_cnt > 127) + debounce_cnt = 127; + + /* up to 255 * 8 ADC clocks are possible */ + if (settling_cnt > 255) + settling_cnt = 255; + + error = imx25_setup_queue_4wire(priv, settling_cnt, &itemct); + if (error) + return error; + + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_LITEMID_MASK | MX25_ADCQ_CR_WMRK_MASK, + MX25_ADCQ_CR_LITEMID(itemct - 1) | + MX25_ADCQ_CR_WMRK(priv->expected_samples - 1)); + + /* setup debounce count */ + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, + MX25_TGCR_PDBTIME_MASK, + MX25_TGCR_PDBTIME(debounce_cnt)); + + /* enable debounce */ + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDBEN, + MX25_TGCR_PDBEN); + regmap_update_bits(priv->core_regs, MX25_TSC_TGCR, MX25_TGCR_PDEN, + MX25_TGCR_PDEN); + + /* enable the engine on demand */ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, MX25_ADCQ_CR_QSM_MASK, + MX25_ADCQ_CR_QSM_FQS); + + /* Enable repeat and repeat wait */ + regmap_update_bits(priv->regs, MX25_ADCQ_CR, + MX25_ADCQ_CR_RPT | MX25_ADCQ_CR_RWAIT_MASK, + MX25_ADCQ_CR_RPT | + MX25_ADCQ_CR_RWAIT(MX25_TSC_REPEAT_WAIT)); + + return 0; +} + +static int mx25_tcq_parse_dt(struct platform_device *pdev, + struct mx25_tcq_priv *priv) +{ + struct device_node *np = pdev->dev.of_node; + u32 wires; + int error; + + /* Setup defaults */ + priv->pen_threshold = 500; + priv->sample_count = 3; + priv->pen_debounce = 1000000; + priv->settling_time = 250000; + + error = of_property_read_u32(np, "fsl,wires", &wires); + if (error) { + dev_err(&pdev->dev, "Failed to find fsl,wires properties\n"); + return error; + } + + if (wires == 4) { + priv->mode = MX25_TS_4WIRE; + } else { + dev_err(&pdev->dev, "%u-wire mode not supported\n", wires); + return -EINVAL; + } + + /* These are optional, we don't care about the return values */ + of_property_read_u32(np, "fsl,pen-threshold", &priv->pen_threshold); + of_property_read_u32(np, "fsl,settling-time-ns", &priv->settling_time); + of_property_read_u32(np, "fsl,pen-debounce-ns", &priv->pen_debounce); + + return 0; +} + +static int mx25_tcq_open(struct input_dev *idev) +{ + struct device *dev = &idev->dev; + struct mx25_tcq_priv *priv = dev_get_drvdata(dev); + int error; + + error = clk_prepare_enable(priv->clk); + if (error) { + dev_err(dev, "Failed to enable ipg clock\n"); + return error; + } + + error = mx25_tcq_init(priv); + if (error) { + dev_err(dev, "Failed to init tcq\n"); + clk_disable_unprepare(priv->clk); + return error; + } + + mx25_tcq_re_enable_touch_detection(priv); + + return 0; +} + +static void mx25_tcq_close(struct input_dev *idev) +{ + struct mx25_tcq_priv *priv = input_get_drvdata(idev); + + mx25_tcq_force_queue_stop(priv); + mx25_tcq_disable_touch_irq(priv); + mx25_tcq_disable_fifo_irq(priv); + clk_disable_unprepare(priv->clk); +} + +static int mx25_tcq_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct input_dev *idev; + struct mx25_tcq_priv *priv; + struct mx25_tsadc *tsadc = dev_get_drvdata(pdev->dev.parent); + struct resource *res; + void __iomem *mem; + int error; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mem = devm_ioremap_resource(dev, res); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + error = mx25_tcq_parse_dt(pdev, priv); + if (error) + return error; + + priv->regs = devm_regmap_init_mmio(dev, mem, &mx25_tcq_regconfig); + if (IS_ERR(priv->regs)) { + dev_err(dev, "Failed to initialize regmap\n"); + return PTR_ERR(priv->regs); + } + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq <= 0) { + dev_err(dev, "Failed to get IRQ\n"); + return priv->irq; + } + + idev = devm_input_allocate_device(dev); + if (!idev) { + dev_err(dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + idev->name = mx25_tcq_name; + input_set_capability(idev, EV_KEY, BTN_TOUCH); + input_set_abs_params(idev, ABS_X, 0, 0xfff, 0, 0); + input_set_abs_params(idev, ABS_Y, 0, 0xfff, 0, 0); + + idev->id.bustype = BUS_HOST; + idev->open = mx25_tcq_open; + idev->close = mx25_tcq_close; + + priv->idev = idev; + input_set_drvdata(idev, priv); + + priv->core_regs = tsadc->regs; + if (!priv->core_regs) + return -EINVAL; + + priv->clk = tsadc->clk; + if (!priv->clk) + return -EINVAL; + + platform_set_drvdata(pdev, priv); + + error = devm_request_threaded_irq(dev, priv->irq, mx25_tcq_irq, + mx25_tcq_irq_thread, 0, pdev->name, + priv); + if (error) { + dev_err(dev, "Failed requesting IRQ\n"); + return error; + } + + error = input_register_device(idev); + if (error) { + dev_err(dev, "Failed to register input device\n"); + return error; + } + + return 0; +} + +static struct platform_driver mx25_tcq_driver = { + .driver = { + .name = "mx25-tcq", + .of_match_table = mx25_tcq_ids, + }, + .probe = mx25_tcq_probe, +}; +module_platform_driver(mx25_tcq_driver); + +MODULE_DESCRIPTION("TS input driver for Freescale mx25"); +MODULE_AUTHOR("Markus Pargmann "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c index 4d113c9..240b16f 100644 --- a/drivers/input/touchscreen/goodix.c +++ b/drivers/input/touchscreen/goodix.c @@ -2,6 +2,7 @@ * Driver for Goodix Touchscreens * * Copyright (c) 2014 Red Hat Inc. + * Copyright (c) 2015 K. Merker * * This code is based on gt9xx.c authored by andrew@goodix.com: * @@ -16,6 +17,8 @@ #include #include +#include +#include #include #include #include @@ -33,11 +36,24 @@ struct goodix_ts_data { struct input_dev *input_dev; int abs_x_max; int abs_y_max; + bool swapped_x_y; + bool inverted_x; + bool inverted_y; unsigned int max_touch_num; unsigned int int_trigger_type; - bool rotated_screen; + int cfg_len; + struct gpio_desc *gpiod_int; + struct gpio_desc *gpiod_rst; + u16 id; + u16 version; + const char *cfg_name; + struct completion firmware_loading_complete; + unsigned long irq_flags; }; +#define GOODIX_GPIO_INT_NAME "irq" +#define GOODIX_GPIO_RST_NAME "reset" + #define GOODIX_MAX_HEIGHT 4096 #define GOODIX_MAX_WIDTH 4096 #define GOODIX_INT_TRIGGER 1 @@ -45,8 +61,13 @@ struct goodix_ts_data { #define GOODIX_MAX_CONTACTS 10 #define GOODIX_CONFIG_MAX_LENGTH 240 +#define GOODIX_CONFIG_911_LENGTH 186 +#define GOODIX_CONFIG_967_LENGTH 228 /* Register defines */ +#define GOODIX_REG_COMMAND 0x8040 +#define GOODIX_CMD_SCREEN_OFF 0x05 + #define GOODIX_READ_COOR_ADDR 0x814E #define GOODIX_REG_CONFIG_DATA 0x8047 #define GOODIX_REG_ID 0x8140 @@ -115,6 +136,63 @@ static int goodix_i2c_read(struct i2c_client *client, return ret < 0 ? ret : (ret != ARRAY_SIZE(msgs) ? -EIO : 0); } +/** + * goodix_i2c_write - write data to a register of the i2c slave device. + * + * @client: i2c device. + * @reg: the register to write to. + * @buf: raw data buffer to write. + * @len: length of the buffer to write + */ +static int goodix_i2c_write(struct i2c_client *client, u16 reg, const u8 *buf, + unsigned len) +{ + u8 *addr_buf; + struct i2c_msg msg; + int ret; + + addr_buf = kmalloc(len + 2, GFP_KERNEL); + if (!addr_buf) + return -ENOMEM; + + addr_buf[0] = reg >> 8; + addr_buf[1] = reg & 0xFF; + memcpy(&addr_buf[2], buf, len); + + msg.flags = 0; + msg.addr = client->addr; + msg.buf = addr_buf; + msg.len = len + 2; + + ret = i2c_transfer(client->adapter, &msg, 1); + kfree(addr_buf); + return ret < 0 ? ret : (ret != 1 ? -EIO : 0); +} + +static int goodix_i2c_write_u8(struct i2c_client *client, u16 reg, u8 value) +{ + return goodix_i2c_write(client, reg, &value, sizeof(value)); +} + +static int goodix_get_cfg_len(u16 id) +{ + switch (id) { + case 911: + case 9271: + case 9110: + case 927: + case 928: + return GOODIX_CONFIG_911_LENGTH; + + case 912: + case 967: + return GOODIX_CONFIG_967_LENGTH; + + default: + return GOODIX_CONFIG_MAX_LENGTH; + } +} + static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data) { int touch_num; @@ -155,10 +233,13 @@ static void goodix_ts_report_touch(struct goodix_ts_data *ts, u8 *coor_data) int input_y = get_unaligned_le16(&coor_data[3]); int input_w = get_unaligned_le16(&coor_data[5]); - if (ts->rotated_screen) { + /* Inversions have to happen before axis swapping */ + if (ts->inverted_x) input_x = ts->abs_x_max - input_x; + if (ts->inverted_y) input_y = ts->abs_y_max - input_y; - } + if (ts->swapped_x_y) + swap(input_x, input_y); input_mt_slot(ts->input_dev, id); input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); @@ -202,21 +283,195 @@ static void goodix_process_events(struct goodix_ts_data *ts) */ static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id) { - static const u8 end_cmd[] = { - GOODIX_READ_COOR_ADDR >> 8, - GOODIX_READ_COOR_ADDR & 0xff, - 0 - }; struct goodix_ts_data *ts = dev_id; goodix_process_events(ts); - if (i2c_master_send(ts->client, end_cmd, sizeof(end_cmd)) < 0) + if (goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0) < 0) dev_err(&ts->client->dev, "I2C write end_cmd error\n"); return IRQ_HANDLED; } +static void goodix_free_irq(struct goodix_ts_data *ts) +{ + devm_free_irq(&ts->client->dev, ts->client->irq, ts); +} + +static int goodix_request_irq(struct goodix_ts_data *ts) +{ + return devm_request_threaded_irq(&ts->client->dev, ts->client->irq, + NULL, goodix_ts_irq_handler, + ts->irq_flags, ts->client->name, ts); +} + +/** + * goodix_check_cfg - Checks if config fw is valid + * + * @ts: goodix_ts_data pointer + * @cfg: firmware config data + */ +static int goodix_check_cfg(struct goodix_ts_data *ts, + const struct firmware *cfg) +{ + int i, raw_cfg_len; + u8 check_sum = 0; + + if (cfg->size > GOODIX_CONFIG_MAX_LENGTH) { + dev_err(&ts->client->dev, + "The length of the config fw is not correct"); + return -EINVAL; + } + + raw_cfg_len = cfg->size - 2; + for (i = 0; i < raw_cfg_len; i++) + check_sum += cfg->data[i]; + check_sum = (~check_sum) + 1; + if (check_sum != cfg->data[raw_cfg_len]) { + dev_err(&ts->client->dev, + "The checksum of the config fw is not correct"); + return -EINVAL; + } + + if (cfg->data[raw_cfg_len + 1] != 1) { + dev_err(&ts->client->dev, + "Config fw must have Config_Fresh register set"); + return -EINVAL; + } + + return 0; +} + +/** + * goodix_send_cfg - Write fw config to device + * + * @ts: goodix_ts_data pointer + * @cfg: config firmware to write to device + */ +static int goodix_send_cfg(struct goodix_ts_data *ts, + const struct firmware *cfg) +{ + int error; + + error = goodix_check_cfg(ts, cfg); + if (error) + return error; + + error = goodix_i2c_write(ts->client, GOODIX_REG_CONFIG_DATA, cfg->data, + cfg->size); + if (error) { + dev_err(&ts->client->dev, "Failed to write config data: %d", + error); + return error; + } + dev_dbg(&ts->client->dev, "Config sent successfully."); + + /* Let the firmware reconfigure itself, so sleep for 10ms */ + usleep_range(10000, 11000); + + return 0; +} + +static int goodix_int_sync(struct goodix_ts_data *ts) +{ + int error; + + error = gpiod_direction_output(ts->gpiod_int, 0); + if (error) + return error; + + msleep(50); /* T5: 50ms */ + + error = gpiod_direction_input(ts->gpiod_int); + if (error) + return error; + + return 0; +} + +/** + * goodix_reset - Reset device during power on + * + * @ts: goodix_ts_data pointer + */ +static int goodix_reset(struct goodix_ts_data *ts) +{ + int error; + + /* begin select I2C slave addr */ + error = gpiod_direction_output(ts->gpiod_rst, 0); + if (error) + return error; + + msleep(20); /* T2: > 10ms */ + + /* HIGH: 0x28/0x29, LOW: 0xBA/0xBB */ + error = gpiod_direction_output(ts->gpiod_int, ts->client->addr == 0x14); + if (error) + return error; + + usleep_range(100, 2000); /* T3: > 100us */ + + error = gpiod_direction_output(ts->gpiod_rst, 1); + if (error) + return error; + + usleep_range(6000, 10000); /* T4: > 5ms */ + + /* end select I2C slave addr */ + error = gpiod_direction_input(ts->gpiod_rst); + if (error) + return error; + + error = goodix_int_sync(ts); + if (error) + return error; + + return 0; +} + +/** + * goodix_get_gpio_config - Get GPIO config from ACPI/DT + * + * @ts: goodix_ts_data pointer + */ +static int goodix_get_gpio_config(struct goodix_ts_data *ts) +{ + int error; + struct device *dev; + struct gpio_desc *gpiod; + + if (!ts->client) + return -EINVAL; + dev = &ts->client->dev; + + /* Get the interrupt GPIO pin number */ + gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_INT_NAME, GPIOD_IN); + if (IS_ERR(gpiod)) { + error = PTR_ERR(gpiod); + if (error != -EPROBE_DEFER) + dev_dbg(dev, "Failed to get %s GPIO: %d\n", + GOODIX_GPIO_INT_NAME, error); + return error; + } + + ts->gpiod_int = gpiod; + + /* Get the reset line GPIO pin number */ + gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_RST_NAME, GPIOD_IN); + if (IS_ERR(gpiod)) { + error = PTR_ERR(gpiod); + if (error != -EPROBE_DEFER) + dev_dbg(dev, "Failed to get %s GPIO: %d\n", + GOODIX_GPIO_RST_NAME, error); + return error; + } + + ts->gpiod_rst = gpiod; + + return 0; +} + /** * goodix_read_config - Read the embedded configuration of the panel * @@ -230,14 +485,15 @@ static void goodix_read_config(struct goodix_ts_data *ts) int error; error = goodix_i2c_read(ts->client, GOODIX_REG_CONFIG_DATA, - config, - GOODIX_CONFIG_MAX_LENGTH); + config, ts->cfg_len); if (error) { dev_warn(&ts->client->dev, "Error reading config (%d), using defaults\n", error); ts->abs_x_max = GOODIX_MAX_WIDTH; ts->abs_y_max = GOODIX_MAX_HEIGHT; + if (ts->swapped_x_y) + swap(ts->abs_x_max, ts->abs_y_max); ts->int_trigger_type = GOODIX_INT_TRIGGER; ts->max_touch_num = GOODIX_MAX_CONTACTS; return; @@ -245,6 +501,8 @@ static void goodix_read_config(struct goodix_ts_data *ts) ts->abs_x_max = get_unaligned_le16(&config[RESOLUTION_LOC]); ts->abs_y_max = get_unaligned_le16(&config[RESOLUTION_LOC + 2]); + if (ts->swapped_x_y) + swap(ts->abs_x_max, ts->abs_y_max); ts->int_trigger_type = config[TRIGGER_LOC] & 0x03; ts->max_touch_num = config[MAX_CONTACTS_LOC] & 0x0f; if (!ts->abs_x_max || !ts->abs_y_max || !ts->max_touch_num) { @@ -252,42 +510,45 @@ static void goodix_read_config(struct goodix_ts_data *ts) "Invalid config, using defaults\n"); ts->abs_x_max = GOODIX_MAX_WIDTH; ts->abs_y_max = GOODIX_MAX_HEIGHT; + if (ts->swapped_x_y) + swap(ts->abs_x_max, ts->abs_y_max); ts->max_touch_num = GOODIX_MAX_CONTACTS; } - ts->rotated_screen = dmi_check_system(rotated_screen); - if (ts->rotated_screen) + if (dmi_check_system(rotated_screen)) { + ts->inverted_x = true; + ts->inverted_y = true; dev_dbg(&ts->client->dev, "Applying '180 degrees rotated screen' quirk\n"); + } } /** * goodix_read_version - Read goodix touchscreen version * - * @client: the i2c client - * @version: output buffer containing the version on success - * @id: output buffer containing the id on success + * @ts: our goodix_ts_data pointer */ -static int goodix_read_version(struct i2c_client *client, u16 *version, u16 *id) +static int goodix_read_version(struct goodix_ts_data *ts) { int error; u8 buf[6]; char id_str[5]; - error = goodix_i2c_read(client, GOODIX_REG_ID, buf, sizeof(buf)); + error = goodix_i2c_read(ts->client, GOODIX_REG_ID, buf, sizeof(buf)); if (error) { - dev_err(&client->dev, "read version failed: %d\n", error); + dev_err(&ts->client->dev, "read version failed: %d\n", error); return error; } memcpy(id_str, buf, 4); id_str[4] = 0; - if (kstrtou16(id_str, 10, id)) - *id = 0x1001; + if (kstrtou16(id_str, 10, &ts->id)) + ts->id = 0x1001; - *version = get_unaligned_le16(&buf[4]); + ts->version = get_unaligned_le16(&buf[4]); - dev_info(&client->dev, "ID %d, version: %04x\n", *id, *version); + dev_info(&ts->client->dev, "ID %d, version: %04x\n", ts->id, + ts->version); return 0; } @@ -321,13 +582,10 @@ static int goodix_i2c_test(struct i2c_client *client) * goodix_request_input_dev - Allocate, populate and register the input device * * @ts: our goodix_ts_data pointer - * @version: device firmware version - * @id: device ID * * Must be called during probe */ -static int goodix_request_input_dev(struct goodix_ts_data *ts, u16 version, - u16 id) +static int goodix_request_input_dev(struct goodix_ts_data *ts) { int error; @@ -351,8 +609,8 @@ static int goodix_request_input_dev(struct goodix_ts_data *ts, u16 version, ts->input_dev->phys = "input/ts"; ts->input_dev->id.bustype = BUS_I2C; ts->input_dev->id.vendor = 0x0416; - ts->input_dev->id.product = id; - ts->input_dev->id.version = version; + ts->input_dev->id.product = ts->id; + ts->input_dev->id.version = ts->version; error = input_register_device(ts->input_dev); if (error) { @@ -364,13 +622,75 @@ static int goodix_request_input_dev(struct goodix_ts_data *ts, u16 version, return 0; } +/** + * goodix_configure_dev - Finish device initialization + * + * @ts: our goodix_ts_data pointer + * + * Must be called from probe to finish initialization of the device. + * Contains the common initialization code for both devices that + * declare gpio pins and devices that do not. It is either called + * directly from probe or from request_firmware_wait callback. + */ +static int goodix_configure_dev(struct goodix_ts_data *ts) +{ + int error; + + ts->swapped_x_y = device_property_read_bool(&ts->client->dev, + "touchscreen-swapped-x-y"); + ts->inverted_x = device_property_read_bool(&ts->client->dev, + "touchscreen-inverted-x"); + ts->inverted_y = device_property_read_bool(&ts->client->dev, + "touchscreen-inverted-y"); + + goodix_read_config(ts); + + error = goodix_request_input_dev(ts); + if (error) + return error; + + ts->irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT; + error = goodix_request_irq(ts); + if (error) { + dev_err(&ts->client->dev, "request IRQ failed: %d\n", error); + return error; + } + + return 0; +} + +/** + * goodix_config_cb - Callback to finish device init + * + * @ts: our goodix_ts_data pointer + * + * request_firmware_wait callback that finishes + * initialization of the device. + */ +static void goodix_config_cb(const struct firmware *cfg, void *ctx) +{ + struct goodix_ts_data *ts = ctx; + int error; + + if (cfg) { + /* send device configuration to the firmware */ + error = goodix_send_cfg(ts, cfg); + if (error) + goto err_release_cfg; + } + + goodix_configure_dev(ts); + +err_release_cfg: + release_firmware(cfg); + complete_all(&ts->firmware_loading_complete); +} + static int goodix_ts_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct goodix_ts_data *ts; - unsigned long irq_flags; int error; - u16 version_info, id_info; dev_dbg(&client->dev, "I2C Address: 0x%02x\n", client->addr); @@ -385,6 +705,20 @@ static int goodix_ts_probe(struct i2c_client *client, ts->client = client; i2c_set_clientdata(client, ts); + init_completion(&ts->firmware_loading_complete); + + error = goodix_get_gpio_config(ts); + if (error) + return error; + + if (ts->gpiod_int && ts->gpiod_rst) { + /* reset the controller */ + error = goodix_reset(ts); + if (error) { + dev_err(&client->dev, "Controller reset failed.\n"); + return error; + } + } error = goodix_i2c_test(client); if (error) { @@ -392,30 +726,125 @@ static int goodix_ts_probe(struct i2c_client *client, return error; } - error = goodix_read_version(client, &version_info, &id_info); + error = goodix_read_version(ts); if (error) { dev_err(&client->dev, "Read version failed.\n"); return error; } - goodix_read_config(ts); + ts->cfg_len = goodix_get_cfg_len(ts->id); + + if (ts->gpiod_int && ts->gpiod_rst) { + /* update device config */ + ts->cfg_name = devm_kasprintf(&client->dev, GFP_KERNEL, + "goodix_%d_cfg.bin", ts->id); + if (!ts->cfg_name) + return -ENOMEM; + + error = request_firmware_nowait(THIS_MODULE, true, ts->cfg_name, + &client->dev, GFP_KERNEL, ts, + goodix_config_cb); + if (error) { + dev_err(&client->dev, + "Failed to invoke firmware loader: %d\n", + error); + return error; + } - error = goodix_request_input_dev(ts, version_info, id_info); - if (error) - return error; + return 0; + } else { + error = goodix_configure_dev(ts); + if (error) + return error; + } + + return 0; +} + +static int goodix_ts_remove(struct i2c_client *client) +{ + struct goodix_ts_data *ts = i2c_get_clientdata(client); + + if (ts->gpiod_int && ts->gpiod_rst) + wait_for_completion(&ts->firmware_loading_complete); + + return 0; +} + +static int __maybe_unused goodix_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct goodix_ts_data *ts = i2c_get_clientdata(client); + int error; - irq_flags = goodix_irq_flags[ts->int_trigger_type] | IRQF_ONESHOT; - error = devm_request_threaded_irq(&ts->client->dev, client->irq, - NULL, goodix_ts_irq_handler, - irq_flags, client->name, ts); + /* We need gpio pins to suspend/resume */ + if (!ts->gpiod_int || !ts->gpiod_rst) + return 0; + + wait_for_completion(&ts->firmware_loading_complete); + + /* Free IRQ as IRQ pin is used as output in the suspend sequence */ + goodix_free_irq(ts); + + /* Output LOW on the INT pin for 5 ms */ + error = gpiod_direction_output(ts->gpiod_int, 0); if (error) { - dev_err(&client->dev, "request IRQ failed: %d\n", error); + goodix_request_irq(ts); return error; } + usleep_range(5000, 6000); + + error = goodix_i2c_write_u8(ts->client, GOODIX_REG_COMMAND, + GOODIX_CMD_SCREEN_OFF); + if (error) { + dev_err(&ts->client->dev, "Screen off command failed\n"); + gpiod_direction_input(ts->gpiod_int); + goodix_request_irq(ts); + return -EAGAIN; + } + + /* + * The datasheet specifies that the interval between sending screen-off + * command and wake-up should be longer than 58 ms. To avoid waking up + * sooner, delay 58ms here. + */ + msleep(58); + return 0; +} + +static int __maybe_unused goodix_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct goodix_ts_data *ts = i2c_get_clientdata(client); + int error; + + if (!ts->gpiod_int || !ts->gpiod_rst) + return 0; + + /* + * Exit sleep mode by outputting HIGH level to INT pin + * for 2ms~5ms. + */ + error = gpiod_direction_output(ts->gpiod_int, 1); + if (error) + return error; + + usleep_range(2000, 5000); + + error = goodix_int_sync(ts); + if (error) + return error; + + error = goodix_request_irq(ts); + if (error) + return error; + return 0; } +static SIMPLE_DEV_PM_OPS(goodix_pm_ops, goodix_suspend, goodix_resume); + static const struct i2c_device_id goodix_ts_id[] = { { "GDIX1001:00", 0 }, { } @@ -446,11 +875,13 @@ MODULE_DEVICE_TABLE(of, goodix_of_match); static struct i2c_driver goodix_ts_driver = { .probe = goodix_ts_probe, + .remove = goodix_ts_remove, .id_table = goodix_ts_id, .driver = { .name = "Goodix-TS", .acpi_match_table = ACPI_PTR(goodix_acpi_match), .of_match_table = of_match_ptr(goodix_of_match), + .pm = &goodix_pm_ops, }, }; module_i2c_driver(goodix_ts_driver); diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c index ddf694b..fe4848b 100644 --- a/drivers/input/touchscreen/ili210x.c +++ b/drivers/input/touchscreen/ili210x.c @@ -169,7 +169,7 @@ static ssize_t ili210x_calibrate(struct device *dev, return count; } -static DEVICE_ATTR(calibrate, 0644, NULL, ili210x_calibrate); +static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate); static struct attribute *ili210x_attributes[] = { &dev_attr_calibrate.attr, diff --git b/drivers/input/touchscreen/melfas_mip4.c b/drivers/input/touchscreen/melfas_mip4.c new file mode 100644 index 0000000..fb5fb91 --- /dev/null +++ b/drivers/input/touchscreen/melfas_mip4.c @@ -0,0 +1,1543 @@ +/* + * MELFAS MIP4 Touchscreen + * + * Copyright (C) 2016 MELFAS Inc. + * + * Author : Sangwon Jee + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIP4_DEVICE_NAME "mip4_ts" + +/***************************************************************** + * Protocol + * Version : MIP 4.0 Rev 4.6 + *****************************************************************/ + +/* Address */ +#define MIP4_R0_BOOT 0x00 +#define MIP4_R1_BOOT_MODE 0x01 +#define MIP4_R1_BOOT_BUF_ADDR 0x10 +#define MIP4_R1_BOOT_STATUS 0x20 +#define MIP4_R1_BOOT_CMD 0x30 +#define MIP4_R1_BOOT_TARGET_ADDR 0x40 +#define MIP4_R1_BOOT_SIZE 0x44 + +#define MIP4_R0_INFO 0x01 +#define MIP4_R1_INFO_PRODUCT_NAME 0x00 +#define MIP4_R1_INFO_RESOLUTION_X 0x10 +#define MIP4_R1_INFO_RESOLUTION_Y 0x12 +#define MIP4_R1_INFO_NODE_NUM_X 0x14 +#define MIP4_R1_INFO_NODE_NUM_Y 0x15 +#define MIP4_R1_INFO_KEY_NUM 0x16 +#define MIP4_R1_INFO_PRESSURE_NUM 0x17 +#define MIP4_R1_INFO_LENGTH_X 0x18 +#define MIP4_R1_INFO_LENGTH_Y 0x1A +#define MIP4_R1_INFO_PPM_X 0x1C +#define MIP4_R1_INFO_PPM_Y 0x1D +#define MIP4_R1_INFO_VERSION_BOOT 0x20 +#define MIP4_R1_INFO_VERSION_CORE 0x22 +#define MIP4_R1_INFO_VERSION_APP 0x24 +#define MIP4_R1_INFO_VERSION_PARAM 0x26 +#define MIP4_R1_INFO_SECT_BOOT_START 0x30 +#define MIP4_R1_INFO_SECT_BOOT_END 0x31 +#define MIP4_R1_INFO_SECT_CORE_START 0x32 +#define MIP4_R1_INFO_SECT_CORE_END 0x33 +#define MIP4_R1_INFO_SECT_APP_START 0x34 +#define MIP4_R1_INFO_SECT_APP_END 0x35 +#define MIP4_R1_INFO_SECT_PARAM_START 0x36 +#define MIP4_R1_INFO_SECT_PARAM_END 0x37 +#define MIP4_R1_INFO_BUILD_DATE 0x40 +#define MIP4_R1_INFO_BUILD_TIME 0x44 +#define MIP4_R1_INFO_CHECKSUM_PRECALC 0x48 +#define MIP4_R1_INFO_CHECKSUM_REALTIME 0x4A +#define MIP4_R1_INFO_PROTOCOL_NAME 0x50 +#define MIP4_R1_INFO_PROTOCOL_VERSION 0x58 +#define MIP4_R1_INFO_IC_ID 0x70 +#define MIP4_R1_INFO_IC_NAME 0x71 +#define MIP4_R1_INFO_IC_VENDOR_ID 0x75 +#define MIP4_R1_INFO_IC_HW_CATEGORY 0x77 +#define MIP4_R1_INFO_CONTACT_THD_SCR 0x78 +#define MIP4_R1_INFO_CONTACT_THD_KEY 0x7A + +#define MIP4_R0_EVENT 0x02 +#define MIP4_R1_EVENT_SUPPORTED_FUNC 0x00 +#define MIP4_R1_EVENT_FORMAT 0x04 +#define MIP4_R1_EVENT_SIZE 0x06 +#define MIP4_R1_EVENT_PACKET_INFO 0x10 +#define MIP4_R1_EVENT_PACKET_DATA 0x11 + +#define MIP4_R0_CTRL 0x06 +#define MIP4_R1_CTRL_READY_STATUS 0x00 +#define MIP4_R1_CTRL_EVENT_READY 0x01 +#define MIP4_R1_CTRL_MODE 0x10 +#define MIP4_R1_CTRL_EVENT_TRIGGER_TYPE 0x11 +#define MIP4_R1_CTRL_RECALIBRATE 0x12 +#define MIP4_R1_CTRL_POWER_STATE 0x13 +#define MIP4_R1_CTRL_GESTURE_TYPE 0x14 +#define MIP4_R1_CTRL_DISABLE_ESD_ALERT 0x18 +#define MIP4_R1_CTRL_CHARGER_MODE 0x19 +#define MIP4_R1_CTRL_HIGH_SENS_MODE 0x1A +#define MIP4_R1_CTRL_WINDOW_MODE 0x1B +#define MIP4_R1_CTRL_PALM_REJECTION 0x1C +#define MIP4_R1_CTRL_EDGE_CORRECTION 0x1D +#define MIP4_R1_CTRL_ENTER_GLOVE_MODE 0x1E +#define MIP4_R1_CTRL_I2C_ON_LPM 0x1F +#define MIP4_R1_CTRL_GESTURE_DEBUG 0x20 +#define MIP4_R1_CTRL_PALM_EVENT 0x22 +#define MIP4_R1_CTRL_PROXIMITY_SENSING 0x23 + +/* Value */ +#define MIP4_BOOT_MODE_BOOT 0x01 +#define MIP4_BOOT_MODE_APP 0x02 + +#define MIP4_BOOT_STATUS_BUSY 0x05 +#define MIP4_BOOT_STATUS_ERROR 0x0E +#define MIP4_BOOT_STATUS_DONE 0xA0 + +#define MIP4_BOOT_CMD_MASS_ERASE 0x15 +#define MIP4_BOOT_CMD_PROGRAM 0x54 +#define MIP4_BOOT_CMD_ERASE 0x8F +#define MIP4_BOOT_CMD_WRITE 0xA5 +#define MIP4_BOOT_CMD_READ 0xC2 + +#define MIP4_EVENT_INPUT_TYPE_KEY 0 +#define MIP4_EVENT_INPUT_TYPE_SCREEN 1 +#define MIP4_EVENT_INPUT_TYPE_PROXIMITY 2 + +#define I2C_RETRY_COUNT 3 /* 2~ */ + +#define MIP4_BUF_SIZE 128 +#define MIP4_MAX_FINGERS 10 +#define MIP4_MAX_KEYS 4 + +#define MIP4_TOUCH_MAJOR_MIN 0 +#define MIP4_TOUCH_MAJOR_MAX 255 +#define MIP4_TOUCH_MINOR_MIN 0 +#define MIP4_TOUCH_MINOR_MAX 255 +#define MIP4_PRESSURE_MIN 0 +#define MIP4_PRESSURE_MAX 255 + +#define MIP4_FW_NAME "melfas_mip4.fw" +#define MIP4_FW_UPDATE_DEBUG 0 /* 0 (default) or 1 */ + +struct mip4_fw_version { + u16 boot; + u16 core; + u16 app; + u16 param; +}; + +struct mip4_ts { + struct i2c_client *client; + struct input_dev *input; + struct gpio_desc *gpio_ce; + + char phys[32]; + char product_name[16]; + + unsigned int max_x; + unsigned int max_y; + u8 node_x; + u8 node_y; + u8 node_key; + unsigned int ppm_x; + unsigned int ppm_y; + + struct mip4_fw_version fw_version; + + unsigned int event_size; + unsigned int event_format; + + unsigned int key_num; + unsigned short key_code[MIP4_MAX_KEYS]; + + bool wake_irq_enabled; + + u8 buf[MIP4_BUF_SIZE]; +}; + +static int mip4_i2c_xfer(struct mip4_ts *ts, + char *write_buf, unsigned int write_len, + char *read_buf, unsigned int read_len) +{ + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .buf = write_buf, + .len = write_len, + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .buf = read_buf, + .len = read_len, + }, + }; + int retry = I2C_RETRY_COUNT; + int res; + int error; + + do { + res = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (res == ARRAY_SIZE(msg)) + return 0; + + error = res < 0 ? res : -EIO; + dev_err(&ts->client->dev, + "%s - i2c_transfer failed: %d (%d)\n", + __func__, error, res); + } while (--retry); + + return error; +} + +static void mip4_parse_fw_version(const u8 *buf, struct mip4_fw_version *v) +{ + v->boot = get_unaligned_le16(buf + 0); + v->core = get_unaligned_le16(buf + 2); + v->app = get_unaligned_le16(buf + 4); + v->param = get_unaligned_le16(buf + 6); +} + +/* + * Read chip firmware version + */ +static int mip4_get_fw_version(struct mip4_ts *ts) +{ + u8 cmd[] = { MIP4_R0_INFO, MIP4_R1_INFO_VERSION_BOOT }; + u8 buf[sizeof(ts->fw_version)]; + int error; + + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, sizeof(buf)); + if (error) { + memset(&ts->fw_version, 0xff, sizeof(ts->fw_version)); + return error; + } + + mip4_parse_fw_version(buf, &ts->fw_version); + + return 0; +} + +/* + * Fetch device characteristics + */ +static int mip4_query_device(struct mip4_ts *ts) +{ + int error; + u8 cmd[2]; + u8 buf[14]; + + /* Product name */ + cmd[0] = MIP4_R0_INFO; + cmd[1] = MIP4_R1_INFO_PRODUCT_NAME; + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), + ts->product_name, sizeof(ts->product_name)); + if (error) + dev_warn(&ts->client->dev, + "Failed to retrieve product name: %d\n", error); + else + dev_dbg(&ts->client->dev, "product name: %.*s\n", + (int)sizeof(ts->product_name), ts->product_name); + + /* Firmware version */ + error = mip4_get_fw_version(ts); + if (error) + dev_warn(&ts->client->dev, + "Failed to retrieve FW version: %d\n", error); + else + dev_dbg(&ts->client->dev, "F/W Version: %04X %04X %04X %04X\n", + ts->fw_version.boot, ts->fw_version.core, + ts->fw_version.app, ts->fw_version.param); + + /* Resolution */ + cmd[0] = MIP4_R0_INFO; + cmd[1] = MIP4_R1_INFO_RESOLUTION_X; + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 14); + if (error) { + dev_warn(&ts->client->dev, + "Failed to retrieve touchscreen parameters: %d\n", + error); + } else { + ts->max_x = get_unaligned_le16(&buf[0]); + ts->max_y = get_unaligned_le16(&buf[2]); + dev_dbg(&ts->client->dev, "max_x: %d, max_y: %d\n", + ts->max_x, ts->max_y); + + ts->node_x = buf[4]; + ts->node_y = buf[5]; + ts->node_key = buf[6]; + dev_dbg(&ts->client->dev, + "node_x: %d, node_y: %d, node_key: %d\n", + ts->node_x, ts->node_y, ts->node_key); + + ts->ppm_x = buf[12]; + ts->ppm_y = buf[13]; + dev_dbg(&ts->client->dev, "ppm_x: %d, ppm_y: %d\n", + ts->ppm_x, ts->ppm_y); + + /* Key ts */ + if (ts->node_key > 0) + ts->key_num = ts->node_key; + } + + /* Protocol */ + cmd[0] = MIP4_R0_EVENT; + cmd[1] = MIP4_R1_EVENT_SUPPORTED_FUNC; + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), buf, 7); + if (error) { + dev_warn(&ts->client->dev, + "Failed to retrieve device type: %d\n", error); + ts->event_format = 0xff; + } else { + ts->event_format = get_unaligned_le16(&buf[4]); + ts->event_size = buf[6]; + dev_dbg(&ts->client->dev, "event_format: %d, event_size: %d\n", + ts->event_format, ts->event_size); + + if (ts->event_format == 2 || ts->event_format > 3) + dev_warn(&ts->client->dev, + "Unknown event format %d\n", ts->event_format); + } + + return 0; +} + +static int mip4_power_on(struct mip4_ts *ts) +{ + if (ts->gpio_ce) { + gpiod_set_value_cansleep(ts->gpio_ce, 1); + + /* Booting delay : 200~300ms */ + usleep_range(200 * 1000, 300 * 1000); + } + + return 0; +} + +static void mip4_power_off(struct mip4_ts *ts) +{ + if (ts->gpio_ce) + gpiod_set_value_cansleep(ts->gpio_ce, 0); +} + +/* + * Clear touch input event status + */ +static void mip4_clear_input(struct mip4_ts *ts) +{ + int i; + + /* Screen */ + for (i = 0; i < MIP4_MAX_FINGERS; i++) { + input_mt_slot(ts->input, i); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); + } + + /* Keys */ + for (i = 0; i < ts->key_num; i++) + input_report_key(ts->input, ts->key_code[i], 0); + + input_sync(ts->input); +} + +static int mip4_enable(struct mip4_ts *ts) +{ + int error; + + error = mip4_power_on(ts); + if (error) + return error; + + enable_irq(ts->client->irq); + + return 0; +} + +static void mip4_disable(struct mip4_ts *ts) +{ + disable_irq(ts->client->irq); + + mip4_power_off(ts); + + mip4_clear_input(ts); +} + +/***************************************************************** + * Input handling + *****************************************************************/ + +static void mip4_report_keys(struct mip4_ts *ts, u8 *packet) +{ + u8 key; + bool down; + + switch (ts->event_format) { + case 0: + case 1: + key = packet[0] & 0x0F; + down = packet[0] & 0x80; + break; + + case 3: + default: + key = packet[0] & 0x0F; + down = packet[1] & 0x01; + break; + } + + /* Report key event */ + if (key >= 1 && key <= ts->key_num) { + unsigned short keycode = ts->key_code[key - 1]; + + dev_dbg(&ts->client->dev, + "Key - ID: %d, keycode: %d, state: %d\n", + key, keycode, down); + + input_event(ts->input, EV_MSC, MSC_SCAN, keycode); + input_report_key(ts->input, keycode, down); + + } else { + dev_err(&ts->client->dev, "Unknown key: %d\n", key); + } +} + +static void mip4_report_touch(struct mip4_ts *ts, u8 *packet) +{ + int id; + bool hover; + bool palm; + bool state; + u16 x, y; + u8 pressure_stage = 0; + u8 pressure; + u8 size; + u8 touch_major; + u8 touch_minor; + + switch (ts->event_format) { + case 0: + case 1: + /* Touch only */ + state = packet[0] & BIT(7); + hover = packet[0] & BIT(5); + palm = packet[0] & BIT(4); + id = (packet[0] & 0x0F) - 1; + x = ((packet[1] & 0x0F) << 8) | packet[2]; + y = (((packet[1] >> 4) & 0x0F) << 8) | + packet[3]; + pressure = packet[4]; + size = packet[5]; + if (ts->event_format == 0) { + touch_major = packet[5]; + touch_minor = packet[5]; + } else { + touch_major = packet[6]; + touch_minor = packet[7]; + } + break; + + case 3: + default: + /* Touch + Force(Pressure) */ + id = (packet[0] & 0x0F) - 1; + hover = packet[1] & BIT(2); + palm = packet[1] & BIT(1); + state = packet[1] & BIT(0); + x = ((packet[2] & 0x0F) << 8) | packet[3]; + y = (((packet[2] >> 4) & 0x0F) << 8) | + packet[4]; + size = packet[6]; + pressure_stage = (packet[7] & 0xF0) >> 4; + pressure = ((packet[7] & 0x0F) << 8) | + packet[8]; + touch_major = packet[9]; + touch_minor = packet[10]; + break; + } + + dev_dbg(&ts->client->dev, + "Screen - Slot: %d State: %d X: %04d Y: %04d Z: %d\n", + id, state, x, y, pressure); + + if (unlikely(id < 0 || id >= MIP4_MAX_FINGERS)) { + dev_err(&ts->client->dev, "Screen - invalid slot ID: %d\n", id); + } else if (state) { + /* Press or Move event */ + input_mt_slot(ts->input, id); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); + input_report_abs(ts->input, ABS_MT_POSITION_X, x); + input_report_abs(ts->input, ABS_MT_POSITION_Y, y); + input_report_abs(ts->input, ABS_MT_PRESSURE, pressure); + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, touch_major); + input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, touch_minor); + } else { + /* Release event */ + input_mt_slot(ts->input, id); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, 0); + } + + input_mt_sync_frame(ts->input); +} + +static int mip4_handle_packet(struct mip4_ts *ts, u8 *packet) +{ + u8 type; + + switch (ts->event_format) { + case 0: + case 1: + type = (packet[0] & 0x40) >> 6; + break; + + case 3: + type = (packet[0] & 0xF0) >> 4; + break; + + default: + /* Should not happen unless we have corrupted firmware */ + return -EINVAL; + } + + dev_dbg(&ts->client->dev, "Type: %d\n", type); + + /* Report input event */ + switch (type) { + case MIP4_EVENT_INPUT_TYPE_KEY: + mip4_report_keys(ts, packet); + break; + + case MIP4_EVENT_INPUT_TYPE_SCREEN: + mip4_report_touch(ts, packet); + break; + + default: + dev_err(&ts->client->dev, "Unknown event type: %d\n", type); + break; + } + + return 0; +} + +static irqreturn_t mip4_interrupt(int irq, void *dev_id) +{ + struct mip4_ts *ts = dev_id; + struct i2c_client *client = ts->client; + unsigned int i; + int error; + u8 cmd[2]; + u8 size; + bool alert; + + /* Read packet info */ + cmd[0] = MIP4_R0_EVENT; + cmd[1] = MIP4_R1_EVENT_PACKET_INFO; + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, 1); + if (error) { + dev_err(&client->dev, + "Failed to read packet info: %d\n", error); + goto out; + } + + size = ts->buf[0] & 0x7F; + alert = ts->buf[0] & BIT(7); + dev_dbg(&client->dev, "packet size: %d, alert: %d\n", size, alert); + + /* Check size */ + if (!size) { + dev_err(&client->dev, "Empty packet\n"); + goto out; + } + + /* Read packet data */ + cmd[0] = MIP4_R0_EVENT; + cmd[1] = MIP4_R1_EVENT_PACKET_DATA; + error = mip4_i2c_xfer(ts, cmd, sizeof(cmd), ts->buf, size); + if (error) { + dev_err(&client->dev, + "Failed to read packet data: %d\n", error); + goto out; + } + + if (alert) { + dev_dbg(&client->dev, "Alert: %d\n", ts->buf[0]); + } else { + for (i = 0; i < size; i += ts->event_size) { + error = mip4_handle_packet(ts, &ts->buf[i]); + if (error) + break; + } + + input_sync(ts->input); + } + +out: + return IRQ_HANDLED; +} + +static int mip4_input_open(struct input_dev *dev) +{ + struct mip4_ts *ts = input_get_drvdata(dev); + + return mip4_enable(ts); +} + +static void mip4_input_close(struct input_dev *dev) +{ + struct mip4_ts *ts = input_get_drvdata(dev); + + mip4_disable(ts); +} + +/***************************************************************** + * Firmware update + *****************************************************************/ + +/* Firmware Info */ +#define MIP4_BL_PAGE_SIZE 512 /* 512 */ +#define MIP4_BL_PACKET_SIZE 512 /* 512, 256, 128, 64, ... */ + +/* + * Firmware binary tail info + */ + +struct mip4_bin_tail { + u8 tail_mark[4]; + u8 chip_name[4]; + + __le32 bin_start_addr; + __le32 bin_length; + + __le16 ver_boot; + __le16 ver_core; + __le16 ver_app; + __le16 ver_param; + + u8 boot_start; + u8 boot_end; + u8 core_start; + u8 core_end; + u8 app_start; + u8 app_end; + u8 param_start; + u8 param_end; + + u8 checksum_type; + u8 hw_category; + + __le16 param_id; + __le32 param_length; + __le32 build_date; + __le32 build_time; + + __le32 reserved1; + __le32 reserved2; + __le16 reserved3; + __le16 tail_size; + __le32 crc; +} __packed; + +#define MIP4_BIN_TAIL_MARK "MBT\001" +#define MIP4_BIN_TAIL_SIZE (sizeof(struct mip4_bin_tail)) + +/* +* Bootloader - Read status +*/ +static int mip4_bl_read_status(struct mip4_ts *ts) +{ + u8 cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_STATUS }; + u8 result; + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .buf = cmd, + .len = sizeof(cmd), + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .buf = &result, + .len = sizeof(result), + }, + }; + int ret; + int error; + int retry = 1000; + + do { + ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to read bootloader status: %d\n", + error); + return error; + } + + switch (result) { + case MIP4_BOOT_STATUS_DONE: + dev_dbg(&ts->client->dev, "%s - done\n", __func__); + return 0; + + case MIP4_BOOT_STATUS_ERROR: + dev_err(&ts->client->dev, "Bootloader failure\n"); + return -EIO; + + case MIP4_BOOT_STATUS_BUSY: + dev_dbg(&ts->client->dev, "%s - Busy\n", __func__); + error = -EBUSY; + break; + + default: + dev_err(&ts->client->dev, + "Unexpected bootloader status: %#02x\n", + result); + error = -EINVAL; + break; + } + + usleep_range(1000, 2000); + } while (--retry); + + return error; +} + +/* +* Bootloader - Change mode +*/ +static int mip4_bl_change_mode(struct mip4_ts *ts, u8 mode) +{ + u8 mode_chg_cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_MODE, mode }; + u8 mode_read_cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_MODE }; + u8 result; + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .buf = mode_read_cmd, + .len = sizeof(mode_read_cmd), + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .buf = &result, + .len = sizeof(result), + }, + }; + int retry = 10; + int ret; + int error; + + do { + /* Send mode change command */ + ret = i2c_master_send(ts->client, + mode_chg_cmd, sizeof(mode_chg_cmd)); + if (ret != sizeof(mode_chg_cmd)) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send %d mode change: %d (%d)\n", + mode, error, ret); + return error; + } + + dev_dbg(&ts->client->dev, + "Sent mode change request (mode: %d)\n", mode); + + /* Wait */ + msleep(1000); + + /* Verify target mode */ + ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to read device mode: %d\n", error); + return error; + } + + dev_dbg(&ts->client->dev, + "Current device mode: %d, want: %d\n", result, mode); + + if (result == mode) + return 0; + + } while (--retry); + + return -EIO; +} + +/* + * Bootloader - Start bootloader mode + */ +static int mip4_bl_enter(struct mip4_ts *ts) +{ + return mip4_bl_change_mode(ts, MIP4_BOOT_MODE_BOOT); +} + +/* + * Bootloader - Exit bootloader mode + */ +static int mip4_bl_exit(struct mip4_ts *ts) +{ + return mip4_bl_change_mode(ts, MIP4_BOOT_MODE_APP); +} + +static int mip4_bl_get_address(struct mip4_ts *ts, u16 *buf_addr) +{ + u8 cmd[] = { MIP4_R0_BOOT, MIP4_R1_BOOT_BUF_ADDR }; + u8 result[sizeof(u16)]; + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .buf = cmd, + .len = sizeof(cmd), + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .buf = result, + .len = sizeof(result), + }, + }; + int ret; + int error; + + ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to retrieve bootloader buffer address: %d\n", + error); + return error; + } + + *buf_addr = get_unaligned_le16(result); + dev_dbg(&ts->client->dev, + "Bootloader buffer address %#04x\n", *buf_addr); + + return 0; +} + +static int mip4_bl_program_page(struct mip4_ts *ts, int offset, + const u8 *data, int length, u16 buf_addr) +{ + u8 cmd[6]; + u8 *data_buf; + u16 buf_offset; + int ret; + int error; + + dev_dbg(&ts->client->dev, "Writing page @%#06x (%d)\n", + offset, length); + + if (length > MIP4_BL_PAGE_SIZE || length % MIP4_BL_PACKET_SIZE) { + dev_err(&ts->client->dev, + "Invalid page length: %d\n", length); + return -EINVAL; + } + + data_buf = kmalloc(2 + MIP4_BL_PACKET_SIZE, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + + /* Addr */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_TARGET_ADDR; + put_unaligned_le32(offset, &cmd[2]); + ret = i2c_master_send(ts->client, cmd, 6); + if (ret != 6) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send write page address: %d\n", error); + goto out; + } + + /* Size */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_SIZE; + put_unaligned_le32(length, &cmd[2]); + ret = i2c_master_send(ts->client, cmd, 6); + if (ret != 6) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send write page size: %d\n", error); + goto out; + } + + /* Data */ + for (buf_offset = 0; + buf_offset < length; + buf_offset += MIP4_BL_PACKET_SIZE) { + dev_dbg(&ts->client->dev, + "writing chunk at %#04x (size %d)\n", + buf_offset, MIP4_BL_PACKET_SIZE); + put_unaligned_be16(buf_addr + buf_offset, data_buf); + memcpy(&data_buf[2], &data[buf_offset], MIP4_BL_PACKET_SIZE); + ret = i2c_master_send(ts->client, + data_buf, 2 + MIP4_BL_PACKET_SIZE); + if (ret != 2 + MIP4_BL_PACKET_SIZE) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to read chunk at %#04x (size %d): %d\n", + buf_offset, MIP4_BL_PACKET_SIZE, error); + goto out; + } + } + + /* Command */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_CMD; + cmd[2] = MIP4_BOOT_CMD_PROGRAM; + ret = i2c_master_send(ts->client, cmd, 3); + if (ret != 3) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send 'write' command: %d\n", error); + goto out; + } + + /* Status */ + error = mip4_bl_read_status(ts); + +out: + kfree(data_buf); + return error ? error : 0; +} + +static int mip4_bl_verify_page(struct mip4_ts *ts, int offset, + const u8 *data, int length, int buf_addr) +{ + u8 cmd[8]; + u8 *read_buf; + int buf_offset; + struct i2c_msg msg[] = { + { + .addr = ts->client->addr, + .flags = 0, + .buf = cmd, + .len = 2, + }, { + .addr = ts->client->addr, + .flags = I2C_M_RD, + .len = MIP4_BL_PACKET_SIZE, + }, + }; + int ret; + int error; + + dev_dbg(&ts->client->dev, "Validating page @%#06x (%d)\n", + offset, length); + + /* Addr */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_TARGET_ADDR; + put_unaligned_le32(offset, &cmd[2]); + ret = i2c_master_send(ts->client, cmd, 6); + if (ret != 6) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send read page address: %d\n", error); + return error; + } + + /* Size */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_SIZE; + put_unaligned_le32(length, &cmd[2]); + ret = i2c_master_send(ts->client, cmd, 6); + if (ret != 6) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send read page size: %d\n", error); + return error; + } + + /* Command */ + cmd[0] = MIP4_R0_BOOT; + cmd[1] = MIP4_R1_BOOT_CMD; + cmd[2] = MIP4_BOOT_CMD_READ; + ret = i2c_master_send(ts->client, cmd, 3); + if (ret != 3) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to send 'read' command: %d\n", error); + return error; + } + + /* Status */ + error = mip4_bl_read_status(ts); + if (error) + return error; + + /* Read */ + msg[1].buf = read_buf = kmalloc(MIP4_BL_PACKET_SIZE, GFP_KERNEL); + if (!read_buf) + return -ENOMEM; + + for (buf_offset = 0; + buf_offset < length; + buf_offset += MIP4_BL_PACKET_SIZE) { + dev_dbg(&ts->client->dev, + "reading chunk at %#04x (size %d)\n", + buf_offset, MIP4_BL_PACKET_SIZE); + put_unaligned_be16(buf_addr + buf_offset, cmd); + ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); + if (ret != ARRAY_SIZE(msg)) { + error = ret < 0 ? ret : -EIO; + dev_err(&ts->client->dev, + "Failed to read chunk at %#04x (size %d): %d\n", + buf_offset, MIP4_BL_PACKET_SIZE, error); + break; + } + + if (memcmp(&data[buf_offset], read_buf, MIP4_BL_PACKET_SIZE)) { + dev_err(&ts->client->dev, + "Failed to validate chunk at %#04x (size %d)\n", + buf_offset, MIP4_BL_PACKET_SIZE); +#if MIP4_FW_UPDATE_DEBUG + print_hex_dump(KERN_DEBUG, + MIP4_DEVICE_NAME " F/W File: ", + DUMP_PREFIX_OFFSET, 16, 1, + data + offset, MIP4_BL_PACKET_SIZE, + false); + print_hex_dump(KERN_DEBUG, + MIP4_DEVICE_NAME " F/W Chip: ", + DUMP_PREFIX_OFFSET, 16, 1, + read_buf, MIP4_BL_PAGE_SIZE, false); +#endif + error = -EINVAL; + break; + } + } + + kfree(read_buf); + return error ? error : 0; +} + +/* + * Flash chip firmware + */ +static int mip4_flash_fw(struct mip4_ts *ts, + const u8 *fw_data, u32 fw_size, u32 fw_offset) +{ + struct i2c_client *client = ts->client; + int offset; + u16 buf_addr; + int error, error2; + + /* Enter bootloader mode */ + dev_dbg(&client->dev, "Entering bootloader mode\n"); + + error = mip4_bl_enter(ts); + if (error) { + dev_err(&client->dev, + "Failed to enter bootloader mode: %d\n", + error); + return error; + } + + /* Read info */ + error = mip4_bl_get_address(ts, &buf_addr); + if (error) + goto exit_bl; + + /* Program & Verify */ + dev_dbg(&client->dev, + "Program & Verify, page size: %d, packet size: %d\n", + MIP4_BL_PAGE_SIZE, MIP4_BL_PACKET_SIZE); + + for (offset = fw_offset; + offset < fw_offset + fw_size; + offset += MIP4_BL_PAGE_SIZE) { + /* Program */ + error = mip4_bl_program_page(ts, offset, fw_data + offset, + MIP4_BL_PAGE_SIZE, buf_addr); + if (error) + break; + + /* Verify */ + error = mip4_bl_verify_page(ts, offset, fw_data + offset, + MIP4_BL_PAGE_SIZE, buf_addr); + if (error) + break; + } + +exit_bl: + /* Exit bootloader mode */ + dev_dbg(&client->dev, "Exiting bootloader mode\n"); + + error2 = mip4_bl_exit(ts); + if (error2) { + dev_err(&client->dev, + "Failed to exit bootloader mode: %d\n", error2); + if (!error) + error = error2; + } + + /* Reset chip */ + mip4_power_off(ts); + mip4_power_on(ts); + + mip4_query_device(ts); + + /* Refresh device parameters */ + input_set_abs_params(ts->input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0); + input_set_abs_params(ts->input, ABS_X, 0, ts->max_x, 0, 0); + input_set_abs_params(ts->input, ABS_Y, 0, ts->max_y, 0, 0); + input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->ppm_x); + input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->ppm_y); + input_abs_set_res(ts->input, ABS_X, ts->ppm_x); + input_abs_set_res(ts->input, ABS_Y, ts->ppm_y); + + return error ? error : 0; +} + +static int mip4_parse_firmware(struct mip4_ts *ts, const struct firmware *fw, + u32 *fw_offset_start, u32 *fw_size, + const struct mip4_bin_tail **pfw_info) +{ + const struct mip4_bin_tail *fw_info; + struct mip4_fw_version fw_version; + u16 tail_size; + + if (fw->size < MIP4_BIN_TAIL_SIZE) { + dev_err(&ts->client->dev, + "Invalid firmware, size mismatch (tail %zd vs %zd)\n", + MIP4_BIN_TAIL_SIZE, fw->size); + return -EINVAL; + } + + fw_info = (const void *)&fw->data[fw->size - MIP4_BIN_TAIL_SIZE]; + +#if MIP4_FW_UPDATE_DEBUG + print_hex_dump(KERN_ERR, MIP4_DEVICE_NAME " Bin Info: ", + DUMP_PREFIX_OFFSET, 16, 1, *fw_info, tail_size, false); +#endif + + tail_size = get_unaligned_le16(&fw_info->tail_size); + if (tail_size != MIP4_BIN_TAIL_SIZE) { + dev_err(&ts->client->dev, + "wrong tail size: %d (expected %zd)\n", + tail_size, MIP4_BIN_TAIL_SIZE); + return -EINVAL; + } + + /* Check bin format */ + if (memcmp(fw_info->tail_mark, MIP4_BIN_TAIL_MARK, + sizeof(fw_info->tail_mark))) { + dev_err(&ts->client->dev, + "unable to locate tail marker (%*ph vs %*ph)\n", + (int)sizeof(fw_info->tail_mark), fw_info->tail_mark, + (int)sizeof(fw_info->tail_mark), MIP4_BIN_TAIL_MARK); + return -EINVAL; + } + + *fw_offset_start = get_unaligned_le32(&fw_info->bin_start_addr); + *fw_size = get_unaligned_le32(&fw_info->bin_length); + + dev_dbg(&ts->client->dev, + "F/W Data offset: %#08x, size: %d\n", + *fw_offset_start, *fw_size); + + if (*fw_size % MIP4_BL_PAGE_SIZE) { + dev_err(&ts->client->dev, + "encoded fw length %d is not multiple of pages (%d)\n", + *fw_size, MIP4_BL_PAGE_SIZE); + return -EINVAL; + } + + if (fw->size != *fw_offset_start + *fw_size) { + dev_err(&ts->client->dev, + "Wrong firmware size, expected %d bytes, got %zd\n", + *fw_offset_start + *fw_size, fw->size); + return -EINVAL; + } + + mip4_parse_fw_version((const u8 *)&fw_info->ver_boot, &fw_version); + + dev_dbg(&ts->client->dev, + "F/W file version %04X %04X %04X %04X\n", + fw_version.boot, fw_version.core, + fw_version.app, fw_version.param); + + dev_dbg(&ts->client->dev, "F/W chip version: %04X %04X %04X %04X\n", + ts->fw_version.boot, ts->fw_version.core, + ts->fw_version.app, ts->fw_version.param); + + /* Check F/W type */ + if (fw_version.boot != 0xEEEE && fw_version.boot != 0xFFFF && + fw_version.core == 0xEEEE && + fw_version.app == 0xEEEE && + fw_version.param == 0xEEEE) { + dev_dbg(&ts->client->dev, "F/W type: Bootloader\n"); + } else if (fw_version.boot == 0xEEEE && + fw_version.core != 0xEEEE && fw_version.core != 0xFFFF && + fw_version.app != 0xEEEE && fw_version.app != 0xFFFF && + fw_version.param != 0xEEEE && fw_version.param != 0xFFFF) { + dev_dbg(&ts->client->dev, "F/W type: Main\n"); + } else { + dev_err(&ts->client->dev, "Wrong firmware type\n"); + return -EINVAL; + } + + return 0; +} + +static int mip4_execute_fw_update(struct mip4_ts *ts, const struct firmware *fw) +{ + const struct mip4_bin_tail *fw_info; + u32 fw_start_offset; + u32 fw_size; + int retires = 3; + int error; + + error = mip4_parse_firmware(ts, fw, + &fw_start_offset, &fw_size, &fw_info); + if (error) + return error; + + if (ts->input->users) { + disable_irq(ts->client->irq); + } else { + error = mip4_power_on(ts); + if (error) + return error; + } + + /* Update firmware */ + do { + error = mip4_flash_fw(ts, fw->data, fw_size, fw_start_offset); + if (!error) + break; + } while (--retires); + + if (error) + dev_err(&ts->client->dev, + "Failed to flash firmware: %d\n", error); + + /* Enable IRQ */ + if (ts->input->users) + enable_irq(ts->client->irq); + else + mip4_power_off(ts); + + return error ? error : 0; +} + +static ssize_t mip4_sysfs_fw_update(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mip4_ts *ts = i2c_get_clientdata(client); + const struct firmware *fw; + int error; + + error = request_firmware(&fw, MIP4_FW_NAME, dev); + if (error) { + dev_err(&ts->client->dev, + "Failed to retrieve firmware %s: %d\n", + MIP4_FW_NAME, error); + return error; + } + + /* + * Take input mutex to prevent racing with itself and also with + * userspace opening and closing the device and also suspend/resume + * transitions. + */ + mutex_lock(&ts->input->mutex); + + error = mip4_execute_fw_update(ts, fw); + + mutex_unlock(&ts->input->mutex); + + release_firmware(fw); + + if (error) { + dev_err(&ts->client->dev, + "Firmware update failed: %d\n", error); + return error; + } + + return count; +} + +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, mip4_sysfs_fw_update); + +static ssize_t mip4_sysfs_read_fw_version(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mip4_ts *ts = i2c_get_clientdata(client); + size_t count; + + /* Take lock to prevent racing with firmware update */ + mutex_lock(&ts->input->mutex); + + count = snprintf(buf, PAGE_SIZE, "%04X %04X %04X %04X\n", + ts->fw_version.boot, ts->fw_version.core, + ts->fw_version.app, ts->fw_version.param); + + mutex_unlock(&ts->input->mutex); + + return count; +} + +static DEVICE_ATTR(fw_version, S_IRUGO, mip4_sysfs_read_fw_version, NULL); + +static ssize_t mip4_sysfs_read_hw_version(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mip4_ts *ts = i2c_get_clientdata(client); + size_t count; + + /* Take lock to prevent racing with firmware update */ + mutex_lock(&ts->input->mutex); + + /* + * product_name shows the name or version of the hardware + * paired with current firmware in the chip. + */ + count = snprintf(buf, PAGE_SIZE, "%.*s\n", + (int)sizeof(ts->product_name), ts->product_name); + + mutex_unlock(&ts->input->mutex); + + return count; +} + +static DEVICE_ATTR(hw_version, S_IRUGO, mip4_sysfs_read_hw_version, NULL); + +static struct attribute *mip4_attrs[] = { + &dev_attr_fw_version.attr, + &dev_attr_hw_version.attr, + &dev_attr_update_fw.attr, + NULL, +}; + +static const struct attribute_group mip4_attr_group = { + .attrs = mip4_attrs, +}; + +static void mip4_sysfs_remove(void *_data) +{ + struct mip4_ts *ts = _data; + + sysfs_remove_group(&ts->client->dev.kobj, &mip4_attr_group); +} + +static int mip4_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct mip4_ts *ts; + struct input_dev *input; + int error; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "Not supported I2C adapter\n"); + return -ENXIO; + } + + ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + input = devm_input_allocate_device(&client->dev); + if (!input) + return -ENOMEM; + + ts->client = client; + ts->input = input; + + snprintf(ts->phys, sizeof(ts->phys), + "%s/input0", dev_name(&client->dev)); + + ts->gpio_ce = devm_gpiod_get_optional(&client->dev, + "ce", GPIOD_OUT_LOW); + if (IS_ERR(ts->gpio_ce)) { + error = PTR_ERR(ts->gpio_ce); + if (error != EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get gpio: %d\n", error); + return error; + } + + error = mip4_power_on(ts); + if (error) + return error; + error = mip4_query_device(ts); + mip4_power_off(ts); + if (error) + return error; + + input->name = "MELFAS MIP4 Touchscreen"; + input->phys = ts->phys; + + input->id.bustype = BUS_I2C; + input->id.vendor = 0x13c5; + + input->open = mip4_input_open; + input->close = mip4_input_close; + + input_set_drvdata(input, ts); + + input->keycode = ts->key_code; + input->keycodesize = sizeof(*ts->key_code); + input->keycodemax = ts->key_num; + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, ts->max_x, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ts->max_y, 0, 0); + input_set_abs_params(input, ABS_MT_PRESSURE, + MIP4_PRESSURE_MIN, MIP4_PRESSURE_MAX, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, + MIP4_TOUCH_MAJOR_MIN, MIP4_TOUCH_MAJOR_MAX, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, + MIP4_TOUCH_MINOR_MIN, MIP4_TOUCH_MINOR_MAX, 0, 0); + input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->ppm_x); + input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->ppm_y); + + error = input_mt_init_slots(input, MIP4_MAX_FINGERS, INPUT_MT_DIRECT); + if (error) + return error; + + i2c_set_clientdata(client, ts); + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, mip4_interrupt, + IRQF_ONESHOT, MIP4_DEVICE_NAME, ts); + if (error) { + dev_err(&client->dev, + "Failed to request interrupt %d: %d\n", + client->irq, error); + return error; + } + + disable_irq(client->irq); + + error = input_register_device(input); + if (error) { + dev_err(&client->dev, + "Failed to register input device: %d\n", error); + return error; + } + + error = sysfs_create_group(&client->dev.kobj, &mip4_attr_group); + if (error) { + dev_err(&client->dev, + "Failed to create sysfs attribute group: %d\n", error); + return error; + } + + error = devm_add_action(&client->dev, mip4_sysfs_remove, ts); + if (error) { + mip4_sysfs_remove(ts); + dev_err(&client->dev, + "Failed to install sysfs remoce action: %d\n", error); + return error; + } + + return 0; +} + +static int __maybe_unused mip4_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mip4_ts *ts = i2c_get_clientdata(client); + struct input_dev *input = ts->input; + + mutex_lock(&input->mutex); + + if (device_may_wakeup(dev)) + ts->wake_irq_enabled = enable_irq_wake(client->irq) == 0; + else if (input->users) + mip4_disable(ts); + + mutex_unlock(&input->mutex); + + return 0; +} + +static int __maybe_unused mip4_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct mip4_ts *ts = i2c_get_clientdata(client); + struct input_dev *input = ts->input; + + mutex_lock(&input->mutex); + + if (ts->wake_irq_enabled) + disable_irq_wake(client->irq); + else if (input->users) + mip4_enable(ts); + + mutex_unlock(&input->mutex); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mip4_pm_ops, mip4_suspend, mip4_resume); + +#ifdef CONFIG_OF +static const struct of_device_id mip4_of_match[] = { + { .compatible = "melfas,"MIP4_DEVICE_NAME, }, + { }, +}; +MODULE_DEVICE_TABLE(of, mip4_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id mip4_acpi_match[] = { + { "MLFS0000", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, mip4_acpi_match); +#endif + +static const struct i2c_device_id mip4_i2c_ids[] = { + { MIP4_DEVICE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mip4_i2c_ids); + +static struct i2c_driver mip4_driver = { + .id_table = mip4_i2c_ids, + .probe = mip4_probe, + .driver = { + .name = MIP4_DEVICE_NAME, + .of_match_table = of_match_ptr(mip4_of_match), + .acpi_match_table = ACPI_PTR(mip4_acpi_match), + .pm = &mip4_pm_ops, + }, +}; +module_i2c_driver(mip4_driver); + +MODULE_DESCRIPTION("MELFAS MIP4 Touchscreen"); +MODULE_VERSION("2016.03.12"); +MODULE_AUTHOR("Sangwon Jee "); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/touchscreen/migor_ts.c b/drivers/input/touchscreen/migor_ts.c index c038db9..02fb119 100644 --- a/drivers/input/touchscreen/migor_ts.c +++ b/drivers/input/touchscreen/migor_ts.c @@ -202,7 +202,7 @@ static int migor_ts_remove(struct i2c_client *client) return 0; } -static int migor_ts_suspend(struct device *dev) +static int __maybe_unused migor_ts_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct migor_ts_priv *priv = i2c_get_clientdata(client); @@ -213,7 +213,7 @@ static int migor_ts_suspend(struct device *dev) return 0; } -static int migor_ts_resume(struct device *dev) +static int __maybe_unused migor_ts_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct migor_ts_priv *priv = i2c_get_clientdata(client); @@ -230,7 +230,7 @@ static const struct i2c_device_id migor_ts_id[] = { { "migor_ts", 0 }, { } }; -MODULE_DEVICE_TABLE(i2c, migor_ts); +MODULE_DEVICE_TABLE(i2c, migor_ts_id); static struct i2c_driver migor_ts_driver = { .driver = { diff --git a/drivers/input/touchscreen/of_touchscreen.c b/drivers/input/touchscreen/of_touchscreen.c index bb6f2fe..8d7f9c8 100644 --- a/drivers/input/touchscreen/of_touchscreen.c +++ b/drivers/input/touchscreen/of_touchscreen.c @@ -55,12 +55,16 @@ static void touchscreen_set_params(struct input_dev *dev, * @input: input device that should be parsed * @multitouch: specifies whether parsed properties should be applied to * single-touch or multi-touch axes + * @prop: pointer to a struct touchscreen_properties into which to store + * axis swap and invert info for use with touchscreen_report_x_y(); + * or %NULL * * This function parses common DT properties for touchscreens and setups the * input device accordingly. The function keeps previously set up default * values if no value is specified via DT. */ -void touchscreen_parse_properties(struct input_dev *input, bool multitouch) +void touchscreen_parse_properties(struct input_dev *input, bool multitouch, + struct touchscreen_properties *prop) { struct device *dev = input->dev.parent; unsigned int axis; @@ -104,5 +108,80 @@ void touchscreen_parse_properties(struct input_dev *input, bool multitouch) &fuzz); if (data_present) touchscreen_set_params(input, axis, maximum, fuzz); + + if (!prop) + return; + + axis = multitouch ? ABS_MT_POSITION_X : ABS_X; + + prop->max_x = input_abs_get_max(input, axis); + prop->max_y = input_abs_get_max(input, axis + 1); + prop->invert_x = + device_property_read_bool(dev, "touchscreen-inverted-x"); + prop->invert_y = + device_property_read_bool(dev, "touchscreen-inverted-y"); + prop->swap_x_y = + device_property_read_bool(dev, "touchscreen-swapped-x-y"); + + if (prop->swap_x_y) + swap(input->absinfo[axis], input->absinfo[axis + 1]); } EXPORT_SYMBOL(touchscreen_parse_properties); + +static void +touchscreen_apply_prop_to_x_y(const struct touchscreen_properties *prop, + unsigned int *x, unsigned int *y) +{ + if (prop->invert_x) + *x = prop->max_x - *x; + + if (prop->invert_y) + *y = prop->max_y - *y; + + if (prop->swap_x_y) + swap(*x, *y); +} + +/** + * touchscreen_set_mt_pos - Set input_mt_pos coordinates + * @pos: input_mt_pos to set coordinates of + * @prop: pointer to a struct touchscreen_properties + * @x: X coordinate to store in pos + * @y: Y coordinate to store in pos + * + * Adjust the passed in x and y values applying any axis inversion and + * swapping requested in the passed in touchscreen_properties and store + * the result in a struct input_mt_pos. + */ +void touchscreen_set_mt_pos(struct input_mt_pos *pos, + const struct touchscreen_properties *prop, + unsigned int x, unsigned int y) +{ + touchscreen_apply_prop_to_x_y(prop, &x, &y); + pos->x = x; + pos->y = y; +} +EXPORT_SYMBOL(touchscreen_set_mt_pos); + +/** + * touchscreen_report_pos - Report touchscreen coordinates + * @input: input_device to report coordinates for + * @prop: pointer to a struct touchscreen_properties + * @x: X coordinate to report + * @y: Y coordinate to report + * @multitouch: Report coordinates on single-touch or multi-touch axes + * + * Adjust the passed in x and y values applying any axis inversion and + * swapping requested in the passed in touchscreen_properties and then + * report the resulting coordinates on the input_dev's x and y axis. + */ +void touchscreen_report_pos(struct input_dev *input, + const struct touchscreen_properties *prop, + unsigned int x, unsigned int y, + bool multitouch) +{ + touchscreen_apply_prop_to_x_y(prop, &x, &y); + input_report_abs(input, multitouch ? ABS_MT_POSITION_X : ABS_X, x); + input_report_abs(input, multitouch ? ABS_MT_POSITION_Y : ABS_Y, y); +} +EXPORT_SYMBOL(touchscreen_report_pos); diff --git a/drivers/input/touchscreen/pcap_ts.c b/drivers/input/touchscreen/pcap_ts.c index 23a354a..0e3fc41 100644 --- a/drivers/input/touchscreen/pcap_ts.c +++ b/drivers/input/touchscreen/pcap_ts.c @@ -87,7 +87,7 @@ static void pcap_ts_read_xy(void *data, u16 res[2]) static void pcap_ts_work(struct work_struct *work) { - struct delayed_work *dw = container_of(work, struct delayed_work, work); + struct delayed_work *dw = to_delayed_work(work); struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work); u8 ch[2]; diff --git a/drivers/input/touchscreen/pixcir_i2c_ts.c b/drivers/input/touchscreen/pixcir_i2c_ts.c index 4b961ad..d159e14 100644 --- a/drivers/input/touchscreen/pixcir_i2c_ts.c +++ b/drivers/input/touchscreen/pixcir_i2c_ts.c @@ -27,9 +27,9 @@ #include #include #include -/*#include */ #include #include +#include #define PIXCIR_MAX_SLOTS 5 /* Max fingers supported by driver */ @@ -38,20 +38,18 @@ struct pixcir_i2c_ts_data { struct input_dev *input; struct gpio_desc *gpio_attb; struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_enable; + struct gpio_desc *gpio_wake; const struct pixcir_i2c_chip_data *chip; + struct touchscreen_properties prop; int max_fingers; /* Max fingers supported in this instance */ bool running; }; -struct pixcir_touch { - int x; - int y; - int id; -}; - struct pixcir_report_data { int num_touches; - struct pixcir_touch touches[PIXCIR_MAX_SLOTS]; + struct input_mt_pos pos[PIXCIR_MAX_SLOTS]; + int ids[PIXCIR_MAX_SLOTS]; }; static void pixcir_ts_parse(struct pixcir_i2c_ts_data *tsdata, @@ -96,11 +94,11 @@ static void pixcir_ts_parse(struct pixcir_i2c_ts_data *tsdata, bufptr = &rdbuf[2]; for (i = 0; i < touch; i++) { - report->touches[i].x = (bufptr[1] << 8) | bufptr[0]; - report->touches[i].y = (bufptr[3] << 8) | bufptr[2]; - + touchscreen_set_mt_pos(&report->pos[i], &tsdata->prop, + get_unaligned_le16(bufptr), + get_unaligned_le16(bufptr + 2)); if (chip->has_hw_ids) { - report->touches[i].id = bufptr[4]; + report->ids[i] = bufptr[4]; bufptr = bufptr + 5; } else { bufptr = bufptr + 4; @@ -111,9 +109,7 @@ static void pixcir_ts_parse(struct pixcir_i2c_ts_data *tsdata, static void pixcir_ts_report(struct pixcir_i2c_ts_data *ts, struct pixcir_report_data *report) { - struct input_mt_pos pos[PIXCIR_MAX_SLOTS]; int slots[PIXCIR_MAX_SLOTS]; - struct pixcir_touch *touch; int n, i, slot; struct device *dev = &ts->client->dev; const struct pixcir_i2c_chip_data *chip = ts->chip; @@ -122,24 +118,16 @@ static void pixcir_ts_report(struct pixcir_i2c_ts_data *ts, if (n > PIXCIR_MAX_SLOTS) n = PIXCIR_MAX_SLOTS; - if (!ts->chip->has_hw_ids) { - for (i = 0; i < n; i++) { - touch = &report->touches[i]; - pos[i].x = touch->x; - pos[i].y = touch->y; - } - - input_mt_assign_slots(ts->input, slots, pos, n, 0); - } + if (!ts->chip->has_hw_ids) + input_mt_assign_slots(ts->input, slots, report->pos, n, 0); for (i = 0; i < n; i++) { - touch = &report->touches[i]; - if (chip->has_hw_ids) { - slot = input_mt_get_slot_by_key(ts->input, touch->id); + slot = input_mt_get_slot_by_key(ts->input, + report->ids[i]); if (slot < 0) { dev_dbg(dev, "no free slot for id 0x%x\n", - touch->id); + report->ids[i]); continue; } } else { @@ -147,14 +135,15 @@ static void pixcir_ts_report(struct pixcir_i2c_ts_data *ts, } input_mt_slot(ts->input, slot); - input_mt_report_slot_state(ts->input, - MT_TOOL_FINGER, true); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); - input_event(ts->input, EV_ABS, ABS_MT_POSITION_X, touch->x); - input_event(ts->input, EV_ABS, ABS_MT_POSITION_Y, touch->y); + input_report_abs(ts->input, ABS_MT_POSITION_X, + report->pos[i].x); + input_report_abs(ts->input, ABS_MT_POSITION_Y, + report->pos[i].y); dev_dbg(dev, "%d: slot %d, x %d, y %d\n", - i, slot, touch->x, touch->y); + i, slot, report->pos[i].x, report->pos[i].y); } input_mt_sync_frame(ts->input); @@ -208,6 +197,11 @@ static int pixcir_set_power_mode(struct pixcir_i2c_ts_data *ts, struct device *dev = &ts->client->dev; int ret; + if (mode == PIXCIR_POWER_ACTIVE || mode == PIXCIR_POWER_IDLE) { + if (ts->gpio_wake) + gpiod_set_value_cansleep(ts->gpio_wake, 1); + } + ret = i2c_smbus_read_byte_data(ts->client, PIXCIR_REG_POWER_MODE); if (ret < 0) { dev_err(dev, "%s: can't read reg 0x%x : %d\n", @@ -228,6 +222,11 @@ static int pixcir_set_power_mode(struct pixcir_i2c_ts_data *ts, return ret; } + if (mode == PIXCIR_POWER_HALT) { + if (ts->gpio_wake) + gpiod_set_value_cansleep(ts->gpio_wake, 0); + } + return 0; } @@ -302,6 +301,11 @@ static int pixcir_start(struct pixcir_i2c_ts_data *ts) struct device *dev = &ts->client->dev; int error; + if (ts->gpio_enable) { + gpiod_set_value_cansleep(ts->gpio_enable, 1); + msleep(100); + } + /* LEVEL_TOUCH interrupt with active low polarity */ error = pixcir_set_int_mode(ts, PIXCIR_INT_LEVEL_TOUCH, 0); if (error) { @@ -343,6 +347,9 @@ static int pixcir_stop(struct pixcir_i2c_ts_data *ts) /* Wait till running ISR is complete */ synchronize_irq(ts->client->irq); + if (ts->gpio_enable) + gpiod_set_value_cansleep(ts->gpio_enable, 0); + return 0; } @@ -495,7 +502,7 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client, } else { input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); - touchscreen_parse_properties(input, true); + touchscreen_parse_properties(input, true, &tsdata->prop); if (!input_abs_get_max(input, ABS_MT_POSITION_X) || !input_abs_get_max(input, ABS_MT_POSITION_Y)) { dev_err(dev, "Touchscreen size is not specified\n"); @@ -534,6 +541,27 @@ static int pixcir_i2c_ts_probe(struct i2c_client *client, return error; } + tsdata->gpio_wake = devm_gpiod_get_optional(dev, "wake", + GPIOD_OUT_HIGH); + if (IS_ERR(tsdata->gpio_wake)) { + error = PTR_ERR(tsdata->gpio_wake); + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get wake gpio: %d\n", error); + return error; + } + + tsdata->gpio_enable = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(tsdata->gpio_enable)) { + error = PTR_ERR(tsdata->gpio_enable); + if (error != -EPROBE_DEFER) + dev_err(dev, "Failed to get enable gpio: %d\n", error); + return error; + } + + if (tsdata->gpio_enable) + msleep(100); + error = devm_request_threaded_irq(dev, client->irq, NULL, pixcir_ts_isr, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, client->name, tsdata); diff --git b/drivers/input/touchscreen/raydium_i2c_ts.c b/drivers/input/touchscreen/raydium_i2c_ts.c new file mode 100644 index 0000000..a99fb5c --- /dev/null +++ b/drivers/input/touchscreen/raydium_i2c_ts.c @@ -0,0 +1,1238 @@ +/* + * Raydium touchscreen I2C driver. + * + * Copyright (C) 2012-2014, Raydium Semiconductor Corporation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, and only version 2, 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. + * + * Raydium reserves the right to make changes without further notice + * to the materials described herein. Raydium does not assume any + * liability arising out of the application described herein. + * + * Contact Raydium Semiconductor Corporation at www.rad-ic.com + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Slave I2C mode */ +#define RM_BOOT_BLDR 0x02 +#define RM_BOOT_MAIN 0x03 + +/* I2C bootoloader commands */ +#define RM_CMD_BOOT_PAGE_WRT 0x0B /* send bl page write */ +#define RM_CMD_BOOT_WRT 0x11 /* send bl write */ +#define RM_CMD_BOOT_ACK 0x22 /* send ack*/ +#define RM_CMD_BOOT_CHK 0x33 /* send data check */ +#define RM_CMD_BOOT_READ 0x44 /* send wait bl data ready*/ + +#define RM_BOOT_RDY 0xFF /* bl data ready */ + +/* I2C main commands */ +#define RM_CMD_QUERY_BANK 0x2B +#define RM_CMD_DATA_BANK 0x4D +#define RM_CMD_ENTER_SLEEP 0x4E +#define RM_CMD_BANK_SWITCH 0xAA + +#define RM_RESET_MSG_ADDR 0x40000004 + +#define RM_MAX_READ_SIZE 56 +#define RM_PACKET_CRC_SIZE 2 + +/* Touch relative info */ +#define RM_MAX_RETRIES 3 +#define RM_MAX_TOUCH_NUM 10 +#define RM_BOOT_DELAY_MS 100 + +/* Offsets in contact data */ +#define RM_CONTACT_STATE_POS 0 +#define RM_CONTACT_X_POS 1 +#define RM_CONTACT_Y_POS 3 +#define RM_CONTACT_PRESSURE_POS 5 +#define RM_CONTACT_WIDTH_X_POS 6 +#define RM_CONTACT_WIDTH_Y_POS 7 + +/* Bootloader relative info */ +#define RM_BL_WRT_CMD_SIZE 3 /* bl flash wrt cmd size */ +#define RM_BL_WRT_PKG_SIZE 32 /* bl wrt pkg size */ +#define RM_BL_WRT_LEN (RM_BL_WRT_PKG_SIZE + RM_BL_WRT_CMD_SIZE) +#define RM_FW_PAGE_SIZE 128 +#define RM_MAX_FW_RETRIES 30 +#define RM_MAX_FW_SIZE 0xD000 + +#define RM_POWERON_DELAY_USEC 500 +#define RM_RESET_DELAY_MSEC 50 + +enum raydium_bl_cmd { + BL_HEADER = 0, + BL_PAGE_STR, + BL_PKG_IDX, + BL_DATA_STR, +}; + +enum raydium_bl_ack { + RAYDIUM_ACK_NULL = 0, + RAYDIUM_WAIT_READY, + RAYDIUM_PATH_READY, +}; + +enum raydium_boot_mode { + RAYDIUM_TS_MAIN = 0, + RAYDIUM_TS_BLDR, +}; + +/* Response to RM_CMD_DATA_BANK request */ +struct raydium_data_info { + __le32 data_bank_addr; + u8 pkg_size; + u8 tp_info_size; +}; + +struct raydium_info { + __le32 hw_ver; /*device version */ + u8 main_ver; + u8 sub_ver; + __le16 ft_ver; /* test version */ + u8 x_num; + u8 y_num; + __le16 x_max; + __le16 y_max; + u8 x_res; /* units/mm */ + u8 y_res; /* units/mm */ +}; + +/* struct raydium_data - represents state of Raydium touchscreen device */ +struct raydium_data { + struct i2c_client *client; + struct input_dev *input; + + struct regulator *avdd; + struct regulator *vccio; + struct gpio_desc *reset_gpio; + + struct raydium_info info; + + struct mutex sysfs_mutex; + + u8 *report_data; + + u32 data_bank_addr; + u8 report_size; + u8 contact_size; + u8 pkg_size; + + enum raydium_boot_mode boot_mode; + + bool wake_irq_enabled; +}; + +static int raydium_i2c_send(struct i2c_client *client, + u8 addr, const void *data, size_t len) +{ + u8 *buf; + int tries = 0; + int ret; + + buf = kmalloc(len + 1, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf[0] = addr; + memcpy(buf + 1, data, len); + + do { + ret = i2c_master_send(client, buf, len + 1); + if (likely(ret == len + 1)) + break; + + msleep(20); + } while (++tries < RM_MAX_RETRIES); + + kfree(buf); + + if (unlikely(ret != len + 1)) { + if (ret >= 0) + ret = -EIO; + dev_err(&client->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + return 0; +} + +static int raydium_i2c_read(struct i2c_client *client, + u8 addr, void *data, size_t len) +{ + struct i2c_msg xfer[] = { + { + .addr = client->addr, + .len = 1, + .buf = &addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = data, + } + }; + int ret; + + ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); + if (unlikely(ret != ARRAY_SIZE(xfer))) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static int raydium_i2c_read_message(struct i2c_client *client, + u32 addr, void *data, size_t len) +{ + __be32 be_addr; + size_t xfer_len; + int error; + + while (len) { + xfer_len = min_t(size_t, len, RM_MAX_READ_SIZE); + + be_addr = cpu_to_be32(addr); + + error = raydium_i2c_send(client, RM_CMD_BANK_SWITCH, + &be_addr, sizeof(be_addr)); + if (!error) + error = raydium_i2c_read(client, addr & 0xff, + data, xfer_len); + if (error) + return error; + + len -= xfer_len; + data += xfer_len; + addr += xfer_len; + } + + return 0; +} + +static int raydium_i2c_send_message(struct i2c_client *client, + u32 addr, const void *data, size_t len) +{ + __be32 be_addr = cpu_to_be32(addr); + int error; + + error = raydium_i2c_send(client, RM_CMD_BANK_SWITCH, + &be_addr, sizeof(be_addr)); + if (!error) + error = raydium_i2c_send(client, addr & 0xff, data, len); + + return error; +} + +static int raydium_i2c_sw_reset(struct i2c_client *client) +{ + const u8 soft_rst_cmd = 0x01; + int error; + + error = raydium_i2c_send_message(client, RM_RESET_MSG_ADDR, + &soft_rst_cmd, sizeof(soft_rst_cmd)); + if (error) { + dev_err(&client->dev, "software reset failed: %d\n", error); + return error; + } + + msleep(RM_RESET_DELAY_MSEC); + + return 0; +} + +static int raydium_i2c_query_ts_info(struct raydium_data *ts) +{ + struct i2c_client *client = ts->client; + struct raydium_data_info data_info; + __le32 query_bank_addr; + + int error, retry_cnt; + + for (retry_cnt = 0; retry_cnt < RM_MAX_RETRIES; retry_cnt++) { + error = raydium_i2c_read(client, RM_CMD_DATA_BANK, + &data_info, sizeof(data_info)); + if (error) + continue; + + /* + * Warn user if we already allocated memory for reports and + * then the size changed (due to firmware update?) and keep + * old size instead. + */ + if (ts->report_data && ts->pkg_size != data_info.pkg_size) { + dev_warn(&client->dev, + "report size changes, was: %d, new: %d\n", + ts->pkg_size, data_info.pkg_size); + } else { + ts->pkg_size = data_info.pkg_size; + ts->report_size = ts->pkg_size - RM_PACKET_CRC_SIZE; + } + + ts->contact_size = data_info.tp_info_size; + ts->data_bank_addr = le32_to_cpu(data_info.data_bank_addr); + + dev_dbg(&client->dev, + "data_bank_addr: %#08x, report_size: %d, contact_size: %d\n", + ts->data_bank_addr, ts->report_size, ts->contact_size); + + error = raydium_i2c_read(client, RM_CMD_QUERY_BANK, + &query_bank_addr, + sizeof(query_bank_addr)); + if (error) + continue; + + error = raydium_i2c_read_message(client, + le32_to_cpu(query_bank_addr), + &ts->info, sizeof(ts->info)); + if (error) + continue; + + return 0; + } + + dev_err(&client->dev, "failed to query device parameters: %d\n", error); + return error; +} + +static int raydium_i2c_check_fw_status(struct raydium_data *ts) +{ + struct i2c_client *client = ts->client; + static const u8 bl_ack = 0x62; + static const u8 main_ack = 0x66; + u8 buf[4]; + int error; + + error = raydium_i2c_read(client, RM_CMD_BOOT_READ, buf, sizeof(buf)); + if (!error) { + if (buf[0] == bl_ack) + ts->boot_mode = RAYDIUM_TS_BLDR; + else if (buf[0] == main_ack) + ts->boot_mode = RAYDIUM_TS_MAIN; + return 0; + } + + return error; +} + +static int raydium_i2c_initialize(struct raydium_data *ts) +{ + struct i2c_client *client = ts->client; + int error, retry_cnt; + + for (retry_cnt = 0; retry_cnt < RM_MAX_RETRIES; retry_cnt++) { + /* Wait for Hello packet */ + msleep(RM_BOOT_DELAY_MS); + + error = raydium_i2c_check_fw_status(ts); + if (error) { + dev_err(&client->dev, + "failed to read 'hello' packet: %d\n", error); + continue; + } + + if (ts->boot_mode == RAYDIUM_TS_BLDR || + ts->boot_mode == RAYDIUM_TS_MAIN) { + break; + } + } + + if (error) + ts->boot_mode = RAYDIUM_TS_BLDR; + + if (ts->boot_mode == RAYDIUM_TS_BLDR) { + ts->info.hw_ver = cpu_to_le32(0xffffffffUL); + ts->info.main_ver = 0xff; + ts->info.sub_ver = 0xff; + } else { + raydium_i2c_query_ts_info(ts); + } + + return error; +} + +static int raydium_i2c_bl_chk_state(struct i2c_client *client, + enum raydium_bl_ack state) +{ + static const u8 ack_ok[] = { 0xFF, 0x39, 0x30, 0x30, 0x54 }; + u8 rbuf[sizeof(ack_ok)]; + u8 retry; + int error; + + for (retry = 0; retry < RM_MAX_FW_RETRIES; retry++) { + switch (state) { + case RAYDIUM_ACK_NULL: + return 0; + + case RAYDIUM_WAIT_READY: + error = raydium_i2c_read(client, RM_CMD_BOOT_CHK, + &rbuf[0], 1); + if (!error && rbuf[0] == RM_BOOT_RDY) + return 0; + + break; + + case RAYDIUM_PATH_READY: + error = raydium_i2c_read(client, RM_CMD_BOOT_CHK, + rbuf, sizeof(rbuf)); + if (!error && !memcmp(rbuf, ack_ok, sizeof(ack_ok))) + return 0; + + break; + + default: + dev_err(&client->dev, "%s: invalid target state %d\n", + __func__, state); + return -EINVAL; + } + + msleep(20); + } + + return -ETIMEDOUT; +} + +static int raydium_i2c_write_object(struct i2c_client *client, + const void *data, size_t len, + enum raydium_bl_ack state) +{ + int error; + + error = raydium_i2c_send(client, RM_CMD_BOOT_WRT, data, len); + if (error) { + dev_err(&client->dev, "WRT obj command failed: %d\n", + error); + return error; + } + + error = raydium_i2c_send(client, RM_CMD_BOOT_ACK, NULL, 0); + if (error) { + dev_err(&client->dev, "Ack obj command failed: %d\n", error); + return error; + } + + error = raydium_i2c_bl_chk_state(client, state); + if (error) { + dev_err(&client->dev, "BL check state failed: %d\n", error); + return error; + } + return 0; +} + +static bool raydium_i2c_boot_trigger(struct i2c_client *client) +{ + static const u8 cmd[7][6] = { + { 0x08, 0x0C, 0x09, 0x00, 0x50, 0xD7 }, + { 0x08, 0x04, 0x09, 0x00, 0x50, 0xA5 }, + { 0x08, 0x04, 0x09, 0x00, 0x50, 0x00 }, + { 0x08, 0x04, 0x09, 0x00, 0x50, 0xA5 }, + { 0x08, 0x0C, 0x09, 0x00, 0x50, 0x00 }, + { 0x06, 0x01, 0x00, 0x00, 0x00, 0x00 }, + { 0x02, 0xA2, 0x00, 0x00, 0x00, 0x00 }, + }; + int i; + int error; + + for (i = 0; i < 7; i++) { + error = raydium_i2c_write_object(client, cmd[i], sizeof(cmd[i]), + RAYDIUM_WAIT_READY); + if (error) { + dev_err(&client->dev, + "boot trigger failed at step %d: %d\n", + i, error); + return error; + } + } + + return 0; +} + +static bool raydium_i2c_fw_trigger(struct i2c_client *client) +{ + static const u8 cmd[5][11] = { + { 0, 0x09, 0x71, 0x0C, 0x09, 0x00, 0x50, 0xD7, 0, 0, 0 }, + { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0xA5, 0, 0, 0 }, + { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0x00, 0, 0, 0 }, + { 0, 0x09, 0x71, 0x04, 0x09, 0x00, 0x50, 0xA5, 0, 0, 0 }, + { 0, 0x09, 0x71, 0x0C, 0x09, 0x00, 0x50, 0x00, 0, 0, 0 }, + }; + int i; + int error; + + for (i = 0; i < 5; i++) { + error = raydium_i2c_write_object(client, cmd[i], sizeof(cmd[i]), + RAYDIUM_ACK_NULL); + if (error) { + dev_err(&client->dev, + "fw trigger failed at step %d: %d\n", + i, error); + return error; + } + } + + return 0; +} + +static int raydium_i2c_check_path(struct i2c_client *client) +{ + static const u8 cmd[] = { 0x09, 0x00, 0x09, 0x00, 0x50, 0x10, 0x00 }; + int error; + + error = raydium_i2c_write_object(client, cmd, sizeof(cmd), + RAYDIUM_PATH_READY); + if (error) { + dev_err(&client->dev, "check path command failed: %d\n", error); + return error; + } + + return 0; +} + +static int raydium_i2c_enter_bl(struct i2c_client *client) +{ + static const u8 cal_cmd[] = { 0x00, 0x01, 0x52 }; + int error; + + error = raydium_i2c_write_object(client, cal_cmd, sizeof(cal_cmd), + RAYDIUM_ACK_NULL); + if (error) { + dev_err(&client->dev, "enter bl command failed: %d\n", error); + return error; + } + + msleep(RM_BOOT_DELAY_MS); + return 0; +} + +static int raydium_i2c_leave_bl(struct i2c_client *client) +{ + static const u8 leave_cmd[] = { 0x05, 0x00 }; + int error; + + error = raydium_i2c_write_object(client, leave_cmd, sizeof(leave_cmd), + RAYDIUM_ACK_NULL); + if (error) { + dev_err(&client->dev, "leave bl command failed: %d\n", error); + return error; + } + + msleep(RM_BOOT_DELAY_MS); + return 0; +} + +static int raydium_i2c_write_checksum(struct i2c_client *client, + size_t length, u16 checksum) +{ + u8 checksum_cmd[] = { 0x00, 0x05, 0x6D, 0x00, 0x00, 0x00, 0x00 }; + int error; + + put_unaligned_le16(length, &checksum_cmd[3]); + put_unaligned_le16(checksum, &checksum_cmd[5]); + + error = raydium_i2c_write_object(client, + checksum_cmd, sizeof(checksum_cmd), + RAYDIUM_ACK_NULL); + if (error) { + dev_err(&client->dev, "failed to write checksum: %d\n", + error); + return error; + } + + return 0; +} + +static int raydium_i2c_disable_watch_dog(struct i2c_client *client) +{ + static const u8 cmd[] = { 0x0A, 0xAA }; + int error; + + error = raydium_i2c_write_object(client, cmd, sizeof(cmd), + RAYDIUM_WAIT_READY); + if (error) { + dev_err(&client->dev, "disable watchdog command failed: %d\n", + error); + return error; + } + + return 0; +} + +static int raydium_i2c_fw_write_page(struct i2c_client *client, + u16 page_idx, const void *data, size_t len) +{ + u8 buf[RM_BL_WRT_LEN]; + size_t xfer_len; + int error; + int i; + + BUILD_BUG_ON((RM_FW_PAGE_SIZE % RM_BL_WRT_PKG_SIZE) != 0); + + for (i = 0; i < RM_FW_PAGE_SIZE / RM_BL_WRT_PKG_SIZE; i++) { + buf[BL_HEADER] = RM_CMD_BOOT_PAGE_WRT; + buf[BL_PAGE_STR] = page_idx ? 0xff : 0; + buf[BL_PKG_IDX] = i + 1; + + xfer_len = min_t(size_t, len, RM_BL_WRT_PKG_SIZE); + memcpy(&buf[BL_DATA_STR], data, xfer_len); + if (len < RM_BL_WRT_PKG_SIZE) + memset(&buf[BL_DATA_STR + xfer_len], 0xff, + RM_BL_WRT_PKG_SIZE - xfer_len); + + error = raydium_i2c_write_object(client, buf, RM_BL_WRT_LEN, + RAYDIUM_WAIT_READY); + if (error) { + dev_err(&client->dev, + "page write command failed for page %d, chunk %d: %d\n", + page_idx, i, error); + return error; + } + + data += xfer_len; + len -= xfer_len; + } + + return error; +} + +static u16 raydium_calc_chksum(const u8 *buf, u16 len) +{ + u16 checksum = 0; + u16 i; + + for (i = 0; i < len; i++) + checksum += buf[i]; + + return checksum; +} + +static int raydium_i2c_do_update_firmware(struct raydium_data *ts, + const struct firmware *fw) +{ + struct i2c_client *client = ts->client; + const void *data; + size_t data_len; + size_t len; + int page_nr; + int i; + int error; + u16 fw_checksum; + + if (fw->size == 0 || fw->size > RM_MAX_FW_SIZE) { + dev_err(&client->dev, "Invalid firmware length\n"); + return -EINVAL; + } + + error = raydium_i2c_check_fw_status(ts); + if (error) { + dev_err(&client->dev, "Unable to access IC %d\n", error); + return error; + } + + if (ts->boot_mode == RAYDIUM_TS_MAIN) { + for (i = 0; i < RM_MAX_RETRIES; i++) { + error = raydium_i2c_enter_bl(client); + if (!error) { + error = raydium_i2c_check_fw_status(ts); + if (error) { + dev_err(&client->dev, + "unable to access IC: %d\n", + error); + return error; + } + + if (ts->boot_mode == RAYDIUM_TS_BLDR) + break; + } + } + + if (ts->boot_mode == RAYDIUM_TS_MAIN) { + dev_err(&client->dev, + "failied to jump to boot loader: %d\n", + error); + return -EIO; + } + } + + error = raydium_i2c_disable_watch_dog(client); + if (error) + return error; + + error = raydium_i2c_check_path(client); + if (error) + return error; + + error = raydium_i2c_boot_trigger(client); + if (error) { + dev_err(&client->dev, "send boot trigger fail: %d\n", error); + return error; + } + + msleep(RM_BOOT_DELAY_MS); + + data = fw->data; + data_len = fw->size; + page_nr = 0; + + while (data_len) { + len = min_t(size_t, data_len, RM_FW_PAGE_SIZE); + + error = raydium_i2c_fw_write_page(client, page_nr++, data, len); + if (error) + return error; + + msleep(20); + + data += len; + data_len -= len; + } + + error = raydium_i2c_leave_bl(client); + if (error) { + dev_err(&client->dev, + "failed to leave boot loader: %d\n", error); + return error; + } + + dev_dbg(&client->dev, "left boot loader mode\n"); + msleep(RM_BOOT_DELAY_MS); + + error = raydium_i2c_check_fw_status(ts); + if (error) { + dev_err(&client->dev, + "failed to check fw status after write: %d\n", + error); + return error; + } + + if (ts->boot_mode != RAYDIUM_TS_MAIN) { + dev_err(&client->dev, + "failed to switch to main fw after writing firmware: %d\n", + error); + return -EINVAL; + } + + error = raydium_i2c_fw_trigger(client); + if (error) { + dev_err(&client->dev, "failed to trigger fw: %d\n", error); + return error; + } + + fw_checksum = raydium_calc_chksum(fw->data, fw->size); + + error = raydium_i2c_write_checksum(client, fw->size, fw_checksum); + if (error) + return error; + + return 0; +} + +static int raydium_i2c_fw_update(struct raydium_data *ts) +{ + struct i2c_client *client = ts->client; + const struct firmware *fw = NULL; + const char *fw_file = "raydium.fw"; + int error; + + error = request_firmware(&fw, fw_file, &client->dev); + if (error) { + dev_err(&client->dev, "Unable to open firmware %s\n", fw_file); + return error; + } + + disable_irq(client->irq); + + error = raydium_i2c_do_update_firmware(ts, fw); + if (error) { + dev_err(&client->dev, "firmware update failed: %d\n", error); + ts->boot_mode = RAYDIUM_TS_BLDR; + goto out_enable_irq; + } + + error = raydium_i2c_initialize(ts); + if (error) { + dev_err(&client->dev, + "failed to initialize device after firmware update: %d\n", + error); + ts->boot_mode = RAYDIUM_TS_BLDR; + goto out_enable_irq; + } + + ts->boot_mode = RAYDIUM_TS_MAIN; + +out_enable_irq: + enable_irq(client->irq); + msleep(100); + + release_firmware(fw); + + return error; +} + +static void raydium_mt_event(struct raydium_data *ts) +{ + int i; + + for (i = 0; i < ts->report_size / ts->contact_size; i++) { + u8 *contact = &ts->report_data[ts->contact_size * i]; + bool state = contact[RM_CONTACT_STATE_POS]; + u8 wx, wy; + + input_mt_slot(ts->input, i); + input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, state); + + if (!state) + continue; + + input_report_abs(ts->input, ABS_MT_POSITION_X, + get_unaligned_le16(&contact[RM_CONTACT_X_POS])); + input_report_abs(ts->input, ABS_MT_POSITION_Y, + get_unaligned_le16(&contact[RM_CONTACT_Y_POS])); + input_report_abs(ts->input, ABS_MT_PRESSURE, + contact[RM_CONTACT_PRESSURE_POS]); + + wx = contact[RM_CONTACT_WIDTH_X_POS]; + wy = contact[RM_CONTACT_WIDTH_Y_POS]; + + input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, max(wx, wy)); + input_report_abs(ts->input, ABS_MT_TOUCH_MINOR, min(wx, wy)); + } + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static irqreturn_t raydium_i2c_irq(int irq, void *_dev) +{ + struct raydium_data *ts = _dev; + int error; + u16 fw_crc; + u16 calc_crc; + + if (ts->boot_mode != RAYDIUM_TS_MAIN) + goto out; + + error = raydium_i2c_read_message(ts->client, ts->data_bank_addr, + ts->report_data, ts->pkg_size); + if (error) + goto out; + + fw_crc = get_unaligned_le16(&ts->report_data[ts->report_size]); + calc_crc = raydium_calc_chksum(ts->report_data, ts->report_size); + if (unlikely(fw_crc != calc_crc)) { + dev_warn(&ts->client->dev, + "%s: invalid packet crc %#04x vs %#04x\n", + __func__, calc_crc, fw_crc); + goto out; + } + + raydium_mt_event(ts); + +out: + return IRQ_HANDLED; +} + +static ssize_t raydium_i2c_fw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + + return sprintf(buf, "%d.%d\n", ts->info.main_ver, ts->info.sub_ver); +} + +static ssize_t raydium_i2c_hw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + + return sprintf(buf, "%#04x\n", le32_to_cpu(ts->info.hw_ver)); +} + +static ssize_t raydium_i2c_boot_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + + return sprintf(buf, "%s\n", + ts->boot_mode == RAYDIUM_TS_MAIN ? + "Normal" : "Recovery"); +} + +static ssize_t raydium_i2c_update_fw_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + int error; + + error = mutex_lock_interruptible(&ts->sysfs_mutex); + if (error) + return error; + + error = raydium_i2c_fw_update(ts); + + mutex_unlock(&ts->sysfs_mutex); + + return error ?: count; +} + +static ssize_t raydium_i2c_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + static const u8 cal_cmd[] = { 0x00, 0x01, 0x9E }; + int error; + + error = mutex_lock_interruptible(&ts->sysfs_mutex); + if (error) + return error; + + error = raydium_i2c_write_object(client, cal_cmd, sizeof(cal_cmd), + RAYDIUM_WAIT_READY); + if (error) + dev_err(&client->dev, "calibrate command failed: %d\n", error); + + mutex_unlock(&ts->sysfs_mutex); + return error ?: count; +} + +static DEVICE_ATTR(fw_version, S_IRUGO, raydium_i2c_fw_ver_show, NULL); +static DEVICE_ATTR(hw_version, S_IRUGO, raydium_i2c_hw_ver_show, NULL); +static DEVICE_ATTR(boot_mode, S_IRUGO, raydium_i2c_boot_mode_show, NULL); +static DEVICE_ATTR(update_fw, S_IWUSR, NULL, raydium_i2c_update_fw_store); +static DEVICE_ATTR(calibrate, S_IWUSR, NULL, raydium_i2c_calibrate_store); + +static struct attribute *raydium_i2c_attributes[] = { + &dev_attr_update_fw.attr, + &dev_attr_boot_mode.attr, + &dev_attr_fw_version.attr, + &dev_attr_hw_version.attr, + &dev_attr_calibrate.attr, + NULL +}; + +static struct attribute_group raydium_i2c_attribute_group = { + .attrs = raydium_i2c_attributes, +}; + +static void raydium_i2c_remove_sysfs_group(void *_data) +{ + struct raydium_data *ts = _data; + + sysfs_remove_group(&ts->client->dev.kobj, &raydium_i2c_attribute_group); +} + +static int raydium_i2c_power_on(struct raydium_data *ts) +{ + int error; + + if (!ts->reset_gpio) + return 0; + + gpiod_set_value_cansleep(ts->reset_gpio, 1); + + error = regulator_enable(ts->avdd); + if (error) { + dev_err(&ts->client->dev, + "failed to enable avdd regulator: %d\n", error); + goto release_reset_gpio; + } + + error = regulator_enable(ts->vccio); + if (error) { + regulator_disable(ts->avdd); + dev_err(&ts->client->dev, + "failed to enable vccio regulator: %d\n", error); + goto release_reset_gpio; + } + + udelay(RM_POWERON_DELAY_USEC); + +release_reset_gpio: + gpiod_set_value_cansleep(ts->reset_gpio, 0); + + if (error) + return error; + + msleep(RM_RESET_DELAY_MSEC); + + return 0; +} + +static void raydium_i2c_power_off(void *_data) +{ + struct raydium_data *ts = _data; + + if (ts->reset_gpio) { + gpiod_set_value_cansleep(ts->reset_gpio, 1); + regulator_disable(ts->vccio); + regulator_disable(ts->avdd); + } +} + +static int raydium_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + union i2c_smbus_data dummy; + struct raydium_data *ts; + int error; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, + "i2c check functionality error (need I2C_FUNC_I2C)\n"); + return -ENXIO; + } + + ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + mutex_init(&ts->sysfs_mutex); + + ts->client = client; + i2c_set_clientdata(client, ts); + + ts->avdd = devm_regulator_get(&client->dev, "avdd"); + if (IS_ERR(ts->avdd)) { + error = PTR_ERR(ts->avdd); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get 'avdd' regulator: %d\n", error); + return error; + } + + ts->vccio = devm_regulator_get(&client->dev, "vccio"); + if (IS_ERR(ts->vccio)) { + error = PTR_ERR(ts->vccio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get 'vccio' regulator: %d\n", error); + return error; + } + + ts->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) { + error = PTR_ERR(ts->reset_gpio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "failed to get reset gpio: %d\n", error); + return error; + } + + error = raydium_i2c_power_on(ts); + if (error) + return error; + + error = devm_add_action(&client->dev, raydium_i2c_power_off, ts); + if (error) { + dev_err(&client->dev, + "failed to install power off action: %d\n", error); + raydium_i2c_power_off(ts); + return error; + } + + /* Make sure there is something at this address */ + if (i2c_smbus_xfer(client->adapter, client->addr, 0, + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &dummy) < 0) { + dev_err(&client->dev, "nothing at this address\n"); + return -ENXIO; + } + + error = raydium_i2c_initialize(ts); + if (error) { + dev_err(&client->dev, "failed to initialize: %d\n", error); + return error; + } + + ts->report_data = devm_kmalloc(&client->dev, + ts->pkg_size, GFP_KERNEL); + if (!ts->report_data) + return -ENOMEM; + + ts->input = devm_input_allocate_device(&client->dev); + if (!ts->input) { + dev_err(&client->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + ts->input->name = "Raydium Touchscreen"; + ts->input->id.bustype = BUS_I2C; + + input_set_drvdata(ts->input, ts); + + input_set_abs_params(ts->input, ABS_MT_POSITION_X, + 0, le16_to_cpu(ts->info.x_max), 0, 0); + input_set_abs_params(ts->input, ABS_MT_POSITION_Y, + 0, le16_to_cpu(ts->info.y_max), 0, 0); + input_abs_set_res(ts->input, ABS_MT_POSITION_X, ts->info.x_res); + input_abs_set_res(ts->input, ABS_MT_POSITION_Y, ts->info.y_res); + + input_set_abs_params(ts->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); + input_set_abs_params(ts->input, ABS_MT_PRESSURE, 0, 255, 0, 0); + + error = input_mt_init_slots(ts->input, RM_MAX_TOUCH_NUM, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); + if (error) { + dev_err(&client->dev, + "failed to initialize MT slots: %d\n", error); + return error; + } + + error = input_register_device(ts->input); + if (error) { + dev_err(&client->dev, + "unable to register input device: %d\n", error); + return error; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, raydium_i2c_irq, + IRQF_ONESHOT, client->name, ts); + if (error) { + dev_err(&client->dev, "Failed to register interrupt\n"); + return error; + } + + error = sysfs_create_group(&client->dev.kobj, + &raydium_i2c_attribute_group); + if (error) { + dev_err(&client->dev, "failed to create sysfs attributes: %d\n", + error); + return error; + } + + error = devm_add_action(&client->dev, + raydium_i2c_remove_sysfs_group, ts); + if (error) { + raydium_i2c_remove_sysfs_group(ts); + dev_err(&client->dev, + "Failed to add sysfs cleanup action: %d\n", error); + return error; + } + + return 0; +} + +static void __maybe_unused raydium_enter_sleep(struct i2c_client *client) +{ + static const u8 sleep_cmd[] = { 0x5A, 0xff, 0x00, 0x0f }; + int error; + + error = raydium_i2c_send(client, RM_CMD_ENTER_SLEEP, + sleep_cmd, sizeof(sleep_cmd)); + if (error) + dev_err(&client->dev, + "sleep command failed: %d\n", error); +} + +static int __maybe_unused raydium_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + + /* Sleep is not available in BLDR recovery mode */ + if (ts->boot_mode != RAYDIUM_TS_MAIN) + return -EBUSY; + + disable_irq(client->irq); + + if (device_may_wakeup(dev)) { + raydium_enter_sleep(client); + + ts->wake_irq_enabled = (enable_irq_wake(client->irq) == 0); + } else { + raydium_i2c_power_off(ts); + } + + return 0; +} + +static int __maybe_unused raydium_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct raydium_data *ts = i2c_get_clientdata(client); + + if (device_may_wakeup(dev)) { + if (ts->wake_irq_enabled) + disable_irq_wake(client->irq); + raydium_i2c_sw_reset(client); + } else { + raydium_i2c_power_on(ts); + raydium_i2c_initialize(ts); + } + + enable_irq(client->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(raydium_i2c_pm_ops, + raydium_i2c_suspend, raydium_i2c_resume); + +static const struct i2c_device_id raydium_i2c_id[] = { + { "raydium_i2c" , 0 }, + { "rm32380", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, raydium_i2c_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id raydium_acpi_id[] = { + { "RAYD0001", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, raydium_acpi_id); +#endif + +#ifdef CONFIG_OF +static const struct of_device_id raydium_of_match[] = { + { .compatible = "raydium,rm32380", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, raydium_of_match); +#endif + +static struct i2c_driver raydium_i2c_driver = { + .probe = raydium_i2c_probe, + .id_table = raydium_i2c_id, + .driver = { + .name = "raydium_ts", + .pm = &raydium_i2c_pm_ops, + .acpi_match_table = ACPI_PTR(raydium_acpi_id), + .of_match_table = of_match_ptr(raydium_of_match), + }, +}; +module_i2c_driver(raydium_i2c_driver); + +MODULE_AUTHOR("Raydium"); +MODULE_DESCRIPTION("Raydium I2c Touchscreen driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/rohm_bu21023.c b/drivers/input/touchscreen/rohm_bu21023.c index afaae25..210640d 100644 --- a/drivers/input/touchscreen/rohm_bu21023.c +++ b/drivers/input/touchscreen/rohm_bu21023.c @@ -725,7 +725,7 @@ static int rohm_ts_load_firmware(struct i2c_client *client, break; error = -EIO; - } while (++retry >= FIRMWARE_RETRY_MAX); + } while (++retry <= FIRMWARE_RETRY_MAX); out: error2 = i2c_smbus_write_byte_data(client, INT_MASK, INT_ALL); diff --git b/drivers/input/touchscreen/silead.c b/drivers/input/touchscreen/silead.c new file mode 100644 index 0000000..b2744a6 --- /dev/null +++ b/drivers/input/touchscreen/silead.c @@ -0,0 +1,565 @@ +/* ------------------------------------------------------------------------- + * Copyright (C) 2014-2015, Intel Corporation + * + * Derived from: + * gslX68X.c + * Copyright (C) 2010-2015, Shanghai Sileadinc Co.Ltd + * + * 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. + * ------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SILEAD_TS_NAME "silead_ts" + +#define SILEAD_REG_RESET 0xE0 +#define SILEAD_REG_DATA 0x80 +#define SILEAD_REG_TOUCH_NR 0x80 +#define SILEAD_REG_POWER 0xBC +#define SILEAD_REG_CLOCK 0xE4 +#define SILEAD_REG_STATUS 0xB0 +#define SILEAD_REG_ID 0xFC +#define SILEAD_REG_MEM_CHECK 0xB0 + +#define SILEAD_STATUS_OK 0x5A5A5A5A +#define SILEAD_TS_DATA_LEN 44 +#define SILEAD_CLOCK 0x04 + +#define SILEAD_CMD_RESET 0x88 +#define SILEAD_CMD_START 0x00 + +#define SILEAD_POINT_DATA_LEN 0x04 +#define SILEAD_POINT_Y_OFF 0x00 +#define SILEAD_POINT_Y_MSB_OFF 0x01 +#define SILEAD_POINT_X_OFF 0x02 +#define SILEAD_POINT_X_MSB_OFF 0x03 +#define SILEAD_TOUCH_ID_MASK 0xF0 + +#define SILEAD_CMD_SLEEP_MIN 10000 +#define SILEAD_CMD_SLEEP_MAX 20000 +#define SILEAD_POWER_SLEEP 20 +#define SILEAD_STARTUP_SLEEP 30 + +#define SILEAD_MAX_FINGERS 10 + +enum silead_ts_power { + SILEAD_POWER_ON = 1, + SILEAD_POWER_OFF = 0 +}; + +struct silead_ts_data { + struct i2c_client *client; + struct gpio_desc *gpio_power; + struct input_dev *input; + char fw_name[64]; + struct touchscreen_properties prop; + u32 max_fingers; + u32 chip_id; + struct input_mt_pos pos[SILEAD_MAX_FINGERS]; + int slots[SILEAD_MAX_FINGERS]; + int id[SILEAD_MAX_FINGERS]; +}; + +struct silead_fw_data { + u32 offset; + u32 val; +}; + +static int silead_ts_request_input_dev(struct silead_ts_data *data) +{ + struct device *dev = &data->client->dev; + int error; + + data->input = devm_input_allocate_device(dev); + if (!data->input) { + dev_err(dev, + "Failed to allocate input device\n"); + return -ENOMEM; + } + + input_set_abs_params(data->input, ABS_MT_POSITION_X, 0, 4095, 0, 0); + input_set_abs_params(data->input, ABS_MT_POSITION_Y, 0, 4095, 0, 0); + touchscreen_parse_properties(data->input, true, &data->prop); + + input_mt_init_slots(data->input, data->max_fingers, + INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED | + INPUT_MT_TRACK); + + data->input->name = SILEAD_TS_NAME; + data->input->phys = "input/ts"; + data->input->id.bustype = BUS_I2C; + + error = input_register_device(data->input); + if (error) { + dev_err(dev, "Failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +static void silead_ts_set_power(struct i2c_client *client, + enum silead_ts_power state) +{ + struct silead_ts_data *data = i2c_get_clientdata(client); + + if (data->gpio_power) { + gpiod_set_value_cansleep(data->gpio_power, state); + msleep(SILEAD_POWER_SLEEP); + } +} + +static void silead_ts_read_data(struct i2c_client *client) +{ + struct silead_ts_data *data = i2c_get_clientdata(client); + struct input_dev *input = data->input; + struct device *dev = &client->dev; + u8 *bufp, buf[SILEAD_TS_DATA_LEN]; + int touch_nr, error, i; + + error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_DATA, + SILEAD_TS_DATA_LEN, buf); + if (error < 0) { + dev_err(dev, "Data read error %d\n", error); + return; + } + + touch_nr = buf[0]; + if (touch_nr > data->max_fingers) { + dev_warn(dev, "More touches reported then supported %d > %d\n", + touch_nr, data->max_fingers); + touch_nr = data->max_fingers; + } + + bufp = buf + SILEAD_POINT_DATA_LEN; + for (i = 0; i < touch_nr; i++, bufp += SILEAD_POINT_DATA_LEN) { + /* Bits 4-7 are the touch id */ + data->id[i] = (bufp[SILEAD_POINT_X_MSB_OFF] & + SILEAD_TOUCH_ID_MASK) >> 4; + touchscreen_set_mt_pos(&data->pos[i], &data->prop, + get_unaligned_le16(&bufp[SILEAD_POINT_X_OFF]) & 0xfff, + get_unaligned_le16(&bufp[SILEAD_POINT_Y_OFF]) & 0xfff); + } + + input_mt_assign_slots(input, data->slots, data->pos, touch_nr, 0); + + for (i = 0; i < touch_nr; i++) { + input_mt_slot(input, data->slots[i]); + input_mt_report_slot_state(input, MT_TOOL_FINGER, true); + input_report_abs(input, ABS_MT_POSITION_X, data->pos[i].x); + input_report_abs(input, ABS_MT_POSITION_Y, data->pos[i].y); + + dev_dbg(dev, "x=%d y=%d hw_id=%d sw_id=%d\n", data->pos[i].x, + data->pos[i].y, data->id[i], data->slots[i]); + } + + input_mt_sync_frame(input); + input_sync(input); +} + +static int silead_ts_init(struct i2c_client *client) +{ + struct silead_ts_data *data = i2c_get_clientdata(client); + int error; + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET, + SILEAD_CMD_RESET); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_TOUCH_NR, + data->max_fingers); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_CLOCK, + SILEAD_CLOCK); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET, + SILEAD_CMD_START); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + return 0; + +i2c_write_err: + dev_err(&client->dev, "Registers clear error %d\n", error); + return error; +} + +static int silead_ts_reset(struct i2c_client *client) +{ + int error; + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET, + SILEAD_CMD_RESET); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_CLOCK, + SILEAD_CLOCK); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_POWER, + SILEAD_CMD_START); + if (error) + goto i2c_write_err; + usleep_range(SILEAD_CMD_SLEEP_MIN, SILEAD_CMD_SLEEP_MAX); + + return 0; + +i2c_write_err: + dev_err(&client->dev, "Chip reset error %d\n", error); + return error; +} + +static int silead_ts_startup(struct i2c_client *client) +{ + int error; + + error = i2c_smbus_write_byte_data(client, SILEAD_REG_RESET, 0x00); + if (error) { + dev_err(&client->dev, "Startup error %d\n", error); + return error; + } + + msleep(SILEAD_STARTUP_SLEEP); + + return 0; +} + +static int silead_ts_load_fw(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct silead_ts_data *data = i2c_get_clientdata(client); + unsigned int fw_size, i; + const struct firmware *fw; + struct silead_fw_data *fw_data; + int error; + + dev_dbg(dev, "Firmware file name: %s", data->fw_name); + + error = request_firmware(&fw, data->fw_name, dev); + if (error) { + dev_err(dev, "Firmware request error %d\n", error); + return error; + } + + fw_size = fw->size / sizeof(*fw_data); + fw_data = (struct silead_fw_data *)fw->data; + + for (i = 0; i < fw_size; i++) { + error = i2c_smbus_write_i2c_block_data(client, + fw_data[i].offset, + 4, + (u8 *)&fw_data[i].val); + if (error) { + dev_err(dev, "Firmware load error %d\n", error); + break; + } + } + + release_firmware(fw); + return error ?: 0; +} + +static u32 silead_ts_get_status(struct i2c_client *client) +{ + int error; + __le32 status; + + error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_STATUS, + sizeof(status), (u8 *)&status); + if (error < 0) { + dev_err(&client->dev, "Status read error %d\n", error); + return error; + } + + return le32_to_cpu(status); +} + +static int silead_ts_get_id(struct i2c_client *client) +{ + struct silead_ts_data *data = i2c_get_clientdata(client); + __le32 chip_id; + int error; + + error = i2c_smbus_read_i2c_block_data(client, SILEAD_REG_ID, + sizeof(chip_id), (u8 *)&chip_id); + if (error < 0) { + dev_err(&client->dev, "Chip ID read error %d\n", error); + return error; + } + + data->chip_id = le32_to_cpu(chip_id); + dev_info(&client->dev, "Silead chip ID: 0x%8X", data->chip_id); + + return 0; +} + +static int silead_ts_setup(struct i2c_client *client) +{ + int error; + u32 status; + + silead_ts_set_power(client, SILEAD_POWER_OFF); + silead_ts_set_power(client, SILEAD_POWER_ON); + + error = silead_ts_get_id(client); + if (error) + return error; + + error = silead_ts_init(client); + if (error) + return error; + + error = silead_ts_reset(client); + if (error) + return error; + + error = silead_ts_load_fw(client); + if (error) + return error; + + error = silead_ts_startup(client); + if (error) + return error; + + status = silead_ts_get_status(client); + if (status != SILEAD_STATUS_OK) { + dev_err(&client->dev, + "Initialization error, status: 0x%X\n", status); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t silead_ts_threaded_irq_handler(int irq, void *id) +{ + struct silead_ts_data *data = id; + struct i2c_client *client = data->client; + + silead_ts_read_data(client); + + return IRQ_HANDLED; +} + +static void silead_ts_read_props(struct i2c_client *client) +{ + struct silead_ts_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + const char *str; + int error; + + error = device_property_read_u32(dev, "silead,max-fingers", + &data->max_fingers); + if (error) { + dev_dbg(dev, "Max fingers read error %d\n", error); + data->max_fingers = 5; /* Most devices handle up-to 5 fingers */ + } + + error = device_property_read_string(dev, "touchscreen-fw-name", &str); + if (!error) + snprintf(data->fw_name, sizeof(data->fw_name), "%s", str); + else + dev_dbg(dev, "Firmware file name read error. Using default."); +} + +#ifdef CONFIG_ACPI +static int silead_ts_set_default_fw_name(struct silead_ts_data *data, + const struct i2c_device_id *id) +{ + const struct acpi_device_id *acpi_id; + struct device *dev = &data->client->dev; + int i; + + if (ACPI_HANDLE(dev)) { + acpi_id = acpi_match_device(dev->driver->acpi_match_table, dev); + if (!acpi_id) + return -ENODEV; + + snprintf(data->fw_name, sizeof(data->fw_name), "%s.fw", + acpi_id->id); + + for (i = 0; i < strlen(data->fw_name); i++) + data->fw_name[i] = tolower(data->fw_name[i]); + } else { + snprintf(data->fw_name, sizeof(data->fw_name), "%s.fw", + id->name); + } + + return 0; +} +#else +static int silead_ts_set_default_fw_name(struct silead_ts_data *data, + const struct i2c_device_id *id) +{ + snprintf(data->fw_name, sizeof(data->fw_name), "%s.fw", id->name); + return 0; +} +#endif + +static int silead_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct silead_ts_data *data; + struct device *dev = &client->dev; + int error; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | + I2C_FUNC_SMBUS_READ_I2C_BLOCK | + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) { + dev_err(dev, "I2C functionality check failed\n"); + return -ENXIO; + } + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->client = client; + + error = silead_ts_set_default_fw_name(data, id); + if (error) + return error; + + silead_ts_read_props(client); + + /* We must have the IRQ provided by DT or ACPI subsytem */ + if (client->irq <= 0) + return -ENODEV; + + /* Power GPIO pin */ + data->gpio_power = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_LOW); + if (IS_ERR(data->gpio_power)) { + if (PTR_ERR(data->gpio_power) != -EPROBE_DEFER) + dev_err(dev, "Shutdown GPIO request failed\n"); + return PTR_ERR(data->gpio_power); + } + + error = silead_ts_setup(client); + if (error) + return error; + + error = silead_ts_request_input_dev(data); + if (error) + return error; + + error = devm_request_threaded_irq(dev, client->irq, + NULL, silead_ts_threaded_irq_handler, + IRQF_ONESHOT, client->name, data); + if (error) { + if (error != -EPROBE_DEFER) + dev_err(dev, "IRQ request failed %d\n", error); + return error; + } + + return 0; +} + +static int __maybe_unused silead_ts_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + silead_ts_set_power(client, SILEAD_POWER_OFF); + return 0; +} + +static int __maybe_unused silead_ts_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + int error, status; + + silead_ts_set_power(client, SILEAD_POWER_ON); + + error = silead_ts_reset(client); + if (error) + return error; + + error = silead_ts_startup(client); + if (error) + return error; + + status = silead_ts_get_status(client); + if (status != SILEAD_STATUS_OK) { + dev_err(dev, "Resume error, status: 0x%02x\n", status); + return -ENODEV; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(silead_ts_pm, silead_ts_suspend, silead_ts_resume); + +static const struct i2c_device_id silead_ts_id[] = { + { "gsl1680", 0 }, + { "gsl1688", 0 }, + { "gsl3670", 0 }, + { "gsl3675", 0 }, + { "gsl3692", 0 }, + { "mssl1680", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, silead_ts_id); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id silead_ts_acpi_match[] = { + { "GSL1680", 0 }, + { "GSL1688", 0 }, + { "GSL3670", 0 }, + { "GSL3675", 0 }, + { "GSL3692", 0 }, + { "MSSL1680", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, silead_ts_acpi_match); +#endif + +static struct i2c_driver silead_ts_driver = { + .probe = silead_ts_probe, + .id_table = silead_ts_id, + .driver = { + .name = SILEAD_TS_NAME, + .acpi_match_table = ACPI_PTR(silead_ts_acpi_match), + .pm = &silead_ts_pm, + }, +}; +module_i2c_driver(silead_ts_driver); + +MODULE_AUTHOR("Robert Dolca "); +MODULE_DESCRIPTION("Silead I2C touchscreen driver"); +MODULE_LICENSE("GPL"); diff --git b/drivers/input/touchscreen/sis_i2c.c b/drivers/input/touchscreen/sis_i2c.c new file mode 100644 index 0000000..8d93f8c --- /dev/null +++ b/drivers/input/touchscreen/sis_i2c.c @@ -0,0 +1,413 @@ +/* + * Touch Screen driver for SiS 9200 family I2C Touch panels + * + * Copyright (C) 2015 SiS, Inc. + * Copyright (C) 2016 Nextfour Group + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIS_I2C_NAME "sis_i2c_ts" + +/* + * The I2C packet format: + * le16 byte count + * u8 Report ID + * + * u8 Number of contacts + * le16 Scan Time (optional) + * le16 CRC + * + * One touch point information consists of 6+ bytes, the order is: + * u8 contact state + * u8 finger id + * le16 x axis + * le16 y axis + * u8 contact width (optional) + * u8 contact height (optional) + * u8 pressure (optional) + * + * Maximum amount of data transmitted in one shot is 64 bytes, if controller + * needs to report more contacts than fit in one packet it will send true + * number of contacts in first packet and 0 as number of contacts in second + * packet. + */ + +#define SIS_MAX_PACKET_SIZE 64 + +#define SIS_PKT_LEN_OFFSET 0 +#define SIS_PKT_REPORT_OFFSET 2 /* Report ID/type */ +#define SIS_PKT_CONTACT_OFFSET 3 /* First contact */ + +#define SIS_SCAN_TIME_LEN 2 + +/* Supported report types */ +#define SIS_ALL_IN_ONE_PACKAGE 0x10 +#define SIS_PKT_IS_TOUCH(x) (((x) & 0x0f) == 0x01) +#define SIS_PKT_IS_HIDI2C(x) (((x) & 0x0f) == 0x06) + +/* Contact properties within report */ +#define SIS_PKT_HAS_AREA(x) ((x) & BIT(4)) +#define SIS_PKT_HAS_PRESSURE(x) ((x) & BIT(5)) +#define SIS_PKT_HAS_SCANTIME(x) ((x) & BIT(6)) + +/* Contact size */ +#define SIS_BASE_LEN_PER_CONTACT 6 +#define SIS_AREA_LEN_PER_CONTACT 2 +#define SIS_PRESSURE_LEN_PER_CONTACT 1 + +/* Offsets within contact data */ +#define SIS_CONTACT_STATUS_OFFSET 0 +#define SIS_CONTACT_ID_OFFSET 1 /* Contact ID */ +#define SIS_CONTACT_X_OFFSET 2 +#define SIS_CONTACT_Y_OFFSET 4 +#define SIS_CONTACT_WIDTH_OFFSET 6 +#define SIS_CONTACT_HEIGHT_OFFSET 7 +#define SIS_CONTACT_PRESSURE_OFFSET(id) (SIS_PKT_HAS_AREA(id) ? 8 : 6) + +/* Individual contact state */ +#define SIS_STATUS_UP 0x0 +#define SIS_STATUS_DOWN 0x3 + +/* Touchscreen parameters */ +#define SIS_MAX_FINGERS 10 +#define SIS_MAX_X 4095 +#define SIS_MAX_Y 4095 +#define SIS_MAX_PRESSURE 255 + +/* Resolution diagonal */ +#define SIS_AREA_LENGTH_LONGER 5792 +/*((SIS_MAX_X^2) + (SIS_MAX_Y^2))^0.5*/ +#define SIS_AREA_LENGTH_SHORT 5792 +#define SIS_AREA_UNIT (5792 / 32) + +struct sis_ts_data { + struct i2c_client *client; + struct input_dev *input; + + struct gpio_desc *attn_gpio; + struct gpio_desc *reset_gpio; + + u8 packet[SIS_MAX_PACKET_SIZE]; +}; + +static int sis_read_packet(struct i2c_client *client, u8 *buf, + unsigned int *num_contacts, + unsigned int *contact_size) +{ + int count_idx; + int ret; + u16 len; + u16 crc, pkg_crc; + u8 report_id; + + ret = i2c_master_recv(client, buf, SIS_MAX_PACKET_SIZE); + if (ret <= 0) + return -EIO; + + len = get_unaligned_le16(&buf[SIS_PKT_LEN_OFFSET]); + if (len > SIS_MAX_PACKET_SIZE) { + dev_err(&client->dev, + "%s: invalid packet length (%d vs %d)\n", + __func__, len, SIS_MAX_PACKET_SIZE); + return -E2BIG; + } + + if (len < 10) + return -EINVAL; + + report_id = buf[SIS_PKT_REPORT_OFFSET]; + count_idx = len - 1; + *contact_size = SIS_BASE_LEN_PER_CONTACT; + + if (report_id != SIS_ALL_IN_ONE_PACKAGE) { + if (SIS_PKT_IS_TOUCH(report_id)) { + /* + * Calculate CRC ignoring packet length + * in the beginning and CRC transmitted + * at the end of the packet. + */ + crc = crc_itu_t(0, buf + 2, len - 2 - 2); + pkg_crc = get_unaligned_le16(&buf[len - 2]); + + if (crc != pkg_crc) { + dev_err(&client->dev, + "%s: CRC Error (%d vs %d)\n", + __func__, crc, pkg_crc); + return -EINVAL; + } + + count_idx -= 2; + + } else if (!SIS_PKT_IS_HIDI2C(report_id)) { + dev_err(&client->dev, + "%s: invalid packet ID %#02x\n", + __func__, report_id); + return -EINVAL; + } + + if (SIS_PKT_HAS_SCANTIME(report_id)) + count_idx -= SIS_SCAN_TIME_LEN; + + if (SIS_PKT_HAS_AREA(report_id)) + *contact_size += SIS_AREA_LEN_PER_CONTACT; + if (SIS_PKT_HAS_PRESSURE(report_id)) + *contact_size += SIS_PRESSURE_LEN_PER_CONTACT; + } + + *num_contacts = buf[count_idx]; + return 0; +} + +static int sis_ts_report_contact(struct sis_ts_data *ts, const u8 *data, u8 id) +{ + struct input_dev *input = ts->input; + int slot; + u8 status = data[SIS_CONTACT_STATUS_OFFSET]; + u8 pressure; + u8 height, width; + u16 x, y; + + if (status != SIS_STATUS_DOWN && status != SIS_STATUS_UP) { + dev_err(&ts->client->dev, "Unexpected touch status: %#02x\n", + data[SIS_CONTACT_STATUS_OFFSET]); + return -EINVAL; + } + + slot = input_mt_get_slot_by_key(input, data[SIS_CONTACT_ID_OFFSET]); + if (slot < 0) + return -ENOENT; + + input_mt_slot(input, slot); + input_mt_report_slot_state(input, MT_TOOL_FINGER, + status == SIS_STATUS_DOWN); + + if (status == SIS_STATUS_DOWN) { + pressure = height = width = 1; + if (id != SIS_ALL_IN_ONE_PACKAGE) { + if (SIS_PKT_HAS_AREA(id)) { + width = data[SIS_CONTACT_WIDTH_OFFSET]; + height = data[SIS_CONTACT_HEIGHT_OFFSET]; + } + + if (SIS_PKT_HAS_PRESSURE(id)) + pressure = + data[SIS_CONTACT_PRESSURE_OFFSET(id)]; + } + + x = get_unaligned_le16(&data[SIS_CONTACT_X_OFFSET]); + y = get_unaligned_le16(&data[SIS_CONTACT_Y_OFFSET]); + + input_report_abs(input, ABS_MT_TOUCH_MAJOR, + width * SIS_AREA_UNIT); + input_report_abs(input, ABS_MT_TOUCH_MINOR, + height * SIS_AREA_UNIT); + input_report_abs(input, ABS_MT_PRESSURE, pressure); + input_report_abs(input, ABS_MT_POSITION_X, x); + input_report_abs(input, ABS_MT_POSITION_Y, y); + } + + return 0; +} + +static void sis_ts_handle_packet(struct sis_ts_data *ts) +{ + const u8 *contact; + unsigned int num_to_report = 0; + unsigned int num_contacts; + unsigned int num_reported; + unsigned int contact_size; + int error; + u8 report_id; + + do { + error = sis_read_packet(ts->client, ts->packet, + &num_contacts, &contact_size); + if (error) + break; + + if (num_to_report == 0) { + num_to_report = num_contacts; + } else if (num_contacts != 0) { + dev_err(&ts->client->dev, + "%s: nonzero (%d) point count in tail packet\n", + __func__, num_contacts); + break; + } + + report_id = ts->packet[SIS_PKT_REPORT_OFFSET]; + contact = &ts->packet[SIS_PKT_CONTACT_OFFSET]; + num_reported = 0; + + while (num_to_report > 0) { + error = sis_ts_report_contact(ts, contact, report_id); + if (error) + break; + + contact += contact_size; + num_to_report--; + num_reported++; + + if (report_id != SIS_ALL_IN_ONE_PACKAGE && + num_reported >= 5) { + /* + * The remainder of contacts is sent + * in the 2nd packet. + */ + break; + } + } + } while (num_to_report > 0); + + input_mt_sync_frame(ts->input); + input_sync(ts->input); +} + +static irqreturn_t sis_ts_irq_handler(int irq, void *dev_id) +{ + struct sis_ts_data *ts = dev_id; + + do { + sis_ts_handle_packet(ts); + } while (ts->attn_gpio && gpiod_get_value_cansleep(ts->attn_gpio)); + + return IRQ_HANDLED; +} + +static void sis_ts_reset(struct sis_ts_data *ts) +{ + if (ts->reset_gpio) { + /* Get out of reset */ + usleep_range(1000, 2000); + gpiod_set_value(ts->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value(ts->reset_gpio, 0); + msleep(100); + } +} + +static int sis_ts_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sis_ts_data *ts; + struct input_dev *input; + int error; + + ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + ts->client = client; + i2c_set_clientdata(client, ts); + + ts->attn_gpio = devm_gpiod_get_optional(&client->dev, + "attn", GPIOD_IN); + if (IS_ERR(ts->attn_gpio)) { + error = PTR_ERR(ts->attn_gpio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get attention GPIO: %d\n", error); + return error; + } + + ts->reset_gpio = devm_gpiod_get_optional(&client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(ts->reset_gpio)) { + error = PTR_ERR(ts->reset_gpio); + if (error != -EPROBE_DEFER) + dev_err(&client->dev, + "Failed to get reset GPIO: %d\n", error); + return error; + } + + sis_ts_reset(ts); + + ts->input = input = devm_input_allocate_device(&client->dev); + if (!input) { + dev_err(&client->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + input->name = "SiS Touchscreen"; + input->id.bustype = BUS_I2C; + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, SIS_MAX_X, 0, 0); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, SIS_MAX_Y, 0, 0); + input_set_abs_params(input, ABS_MT_PRESSURE, 0, SIS_MAX_PRESSURE, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, + 0, SIS_AREA_LENGTH_LONGER, 0, 0); + input_set_abs_params(input, ABS_MT_TOUCH_MINOR, + 0, SIS_AREA_LENGTH_SHORT, 0, 0); + + error = input_mt_init_slots(input, SIS_MAX_FINGERS, INPUT_MT_DIRECT); + if (error) { + dev_err(&client->dev, + "Failed to initialize MT slots: %d\n", error); + return error; + } + + error = devm_request_threaded_irq(&client->dev, client->irq, + NULL, sis_ts_irq_handler, + IRQF_ONESHOT, + client->name, ts); + if (error) { + dev_err(&client->dev, "Failed to request IRQ: %d\n", error); + return error; + } + + error = input_register_device(ts->input); + if (error) { + dev_err(&client->dev, + "Failed to register input device: %d\n", error); + return error; + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sis_ts_dt_ids[] = { + { .compatible = "sis,9200-ts" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sis_ts_dt_ids); +#endif + +static const struct i2c_device_id sis_ts_id[] = { + { SIS_I2C_NAME, 0 }, + { "9200-ts", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, sis_ts_id); + +static struct i2c_driver sis_ts_driver = { + .driver = { + .name = SIS_I2C_NAME, + .of_match_table = of_match_ptr(sis_ts_dt_ids), + }, + .probe = sis_ts_probe, + .id_table = sis_ts_id, +}; +module_i2c_driver(sis_ts_driver); + +MODULE_DESCRIPTION("SiS 9200 Family Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mika Penttilä "); diff --git a/drivers/input/touchscreen/stmpe-ts.c b/drivers/input/touchscreen/stmpe-ts.c index e414d43..2a78e27 100644 --- a/drivers/input/touchscreen/stmpe-ts.c +++ b/drivers/input/touchscreen/stmpe-ts.c @@ -63,6 +63,37 @@ #define STMPE_TS_NAME "stmpe-ts" #define XY_MASK 0xfff +/** + * struct stmpe_touch - stmpe811 touch screen controller state + * @stmpe: pointer back to STMPE MFD container + * @idev: registered input device + * @work: a work item used to scan the device + * @dev: a pointer back to the MFD cell struct device* + * @sample_time: ADC converstion time in number of clock. + * (0 -> 36 clocks, 1 -> 44 clocks, 2 -> 56 clocks, 3 -> 64 clocks, + * 4 -> 80 clocks, 5 -> 96 clocks, 6 -> 144 clocks), + * recommended is 4. + * @mod_12b: ADC Bit mode (0 -> 10bit ADC, 1 -> 12bit ADC) + * @ref_sel: ADC reference source + * (0 -> internal reference, 1 -> external reference) + * @adc_freq: ADC Clock speed + * (0 -> 1.625 MHz, 1 -> 3.25 MHz, 2 || 3 -> 6.5 MHz) + * @ave_ctrl: Sample average control + * (0 -> 1 sample, 1 -> 2 samples, 2 -> 4 samples, 3 -> 8 samples) + * @touch_det_delay: Touch detect interrupt delay + * (0 -> 10 us, 1 -> 50 us, 2 -> 100 us, 3 -> 500 us, + * 4-> 1 ms, 5 -> 5 ms, 6 -> 10 ms, 7 -> 50 ms) + * recommended is 3 + * @settling: Panel driver settling time + * (0 -> 10 us, 1 -> 100 us, 2 -> 500 us, 3 -> 1 ms, + * 4 -> 5 ms, 5 -> 10 ms, 6 for 50 ms, 7 -> 100 ms) + * recommended is 2 + * @fraction_z: Length of the fractional part in z + * (fraction_z ([0..7]) = Count of the fractional part) + * recommended is 7 + * @i_drive: current limit value of the touchscreen drivers + * (0 -> 20 mA typical 35 mA max, 1 -> 50 mA typical 80 mA max) + */ struct stmpe_touch { struct stmpe *stmpe; struct input_dev *idev; diff --git a/drivers/input/touchscreen/sun4i-ts.c b/drivers/input/touchscreen/sun4i-ts.c index 4857943..d07dd29 100644 --- a/drivers/input/touchscreen/sun4i-ts.c +++ b/drivers/input/touchscreen/sun4i-ts.c @@ -115,7 +115,6 @@ struct sun4i_ts_data { struct device *dev; struct input_dev *input; - struct thermal_zone_device *tz; void __iomem *base; unsigned int irq; bool ignore_fifo_data; @@ -366,10 +365,7 @@ static int sun4i_ts_probe(struct platform_device *pdev) if (IS_ERR(hwmon)) return PTR_ERR(hwmon); - ts->tz = thermal_zone_of_sensor_register(ts->dev, 0, ts, - &sun4i_ts_tz_ops); - if (IS_ERR(ts->tz)) - ts->tz = NULL; + devm_thermal_zone_of_sensor_register(ts->dev, 0, ts, &sun4i_ts_tz_ops); writel(TEMP_IRQ_EN(1), ts->base + TP_INT_FIFOC); @@ -377,7 +373,6 @@ static int sun4i_ts_probe(struct platform_device *pdev) error = input_register_device(ts->input); if (error) { writel(0, ts->base + TP_INT_FIFOC); - thermal_zone_of_sensor_unregister(ts->dev, ts->tz); return error; } } @@ -394,8 +389,6 @@ static int sun4i_ts_remove(struct platform_device *pdev) if (ts->input) input_unregister_device(ts->input); - thermal_zone_of_sensor_unregister(ts->dev, ts->tz); - /* Deactivate all IRQs */ writel(0, ts->base + TP_INT_FIFOC); diff --git a/drivers/input/touchscreen/sur40.c b/drivers/input/touchscreen/sur40.c index 45b466e..4ea4757 100644 --- a/drivers/input/touchscreen/sur40.c +++ b/drivers/input/touchscreen/sur40.c @@ -151,7 +151,6 @@ struct sur40_state { struct mutex lock; struct vb2_queue queue; - struct vb2_alloc_ctx *alloc_ctx; struct list_head buf_list; spinlock_t qlock; int sequence; @@ -197,28 +196,34 @@ static int sur40_command(struct sur40_state *dev, static int sur40_init(struct sur40_state *dev) { int result; - u8 buffer[24]; + u8 *buffer; + + buffer = kmalloc(24, GFP_KERNEL); + if (!buffer) { + result = -ENOMEM; + goto error; + } /* stupidly replay the original MS driver init sequence */ result = sur40_command(dev, SUR40_GET_VERSION, 0x00, buffer, 12); if (result < 0) - return result; + goto error; result = sur40_command(dev, SUR40_GET_VERSION, 0x01, buffer, 12); if (result < 0) - return result; + goto error; result = sur40_command(dev, SUR40_GET_VERSION, 0x02, buffer, 12); if (result < 0) - return result; + goto error; result = sur40_command(dev, SUR40_UNKNOWN2, 0x00, buffer, 24); if (result < 0) - return result; + goto error; result = sur40_command(dev, SUR40_UNKNOWN1, 0x00, buffer, 5); if (result < 0) - return result; + goto error; result = sur40_command(dev, SUR40_GET_VERSION, 0x03, buffer, 12); @@ -226,7 +231,8 @@ static int sur40_init(struct sur40_state *dev) * Discard the result buffer - no known data inside except * some version strings, maybe extract these sometime... */ - +error: + kfree(buffer); return result; } @@ -444,7 +450,7 @@ static void sur40_process_video(struct sur40_state *sur40) return; /* mark as finished */ - v4l2_get_timestamp(&new_buf->vb.timestamp); + new_buf->vb.vb2_buf.timestamp = ktime_get_ns(); new_buf->vb.sequence = sur40->sequence++; new_buf->vb.field = V4L2_FIELD_NONE; vb2_buffer_done(&new_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); @@ -573,19 +579,13 @@ static int sur40_probe(struct usb_interface *interface, sur40->queue = sur40_queue; sur40->queue.drv_priv = sur40; sur40->queue.lock = &sur40->lock; + sur40->queue.dev = sur40->dev; /* initialize the queue */ error = vb2_queue_init(&sur40->queue); if (error) goto err_unreg_v4l2; - sur40->alloc_ctx = vb2_dma_sg_init_ctx(sur40->dev); - if (IS_ERR(sur40->alloc_ctx)) { - dev_err(sur40->dev, "Can't allocate buffer context"); - error = PTR_ERR(sur40->alloc_ctx); - goto err_unreg_v4l2; - } - sur40->vdev = sur40_video_device; sur40->vdev.v4l2_dev = &sur40->v4l2; sur40->vdev.lock = &sur40->lock; @@ -626,7 +626,6 @@ static void sur40_disconnect(struct usb_interface *interface) video_unregister_device(&sur40->vdev); v4l2_device_unregister(&sur40->v4l2); - vb2_dma_sg_cleanup_ctx(sur40->alloc_ctx); input_unregister_polled_device(sur40->input); input_free_polled_device(sur40->input); @@ -644,22 +643,18 @@ static void sur40_disconnect(struct usb_interface *interface) * minimum number: many DMA engines need a minimum of 2 buffers in the * queue and you need to have another available for userspace processing. */ -static int sur40_queue_setup(struct vb2_queue *q, const void *parg, +static int sur40_queue_setup(struct vb2_queue *q, unsigned int *nbuffers, unsigned int *nplanes, - unsigned int sizes[], void *alloc_ctxs[]) + unsigned int sizes[], struct device *alloc_devs[]) { - const struct v4l2_format *fmt = parg; - struct sur40_state *sur40 = vb2_get_drv_priv(q); - if (q->num_buffers + *nbuffers < 3) *nbuffers = 3 - q->num_buffers; - if (fmt && fmt->fmt.pix.sizeimage < sur40_video_format.sizeimage) - return -EINVAL; + if (*nplanes) + return sizes[0] < sur40_video_format.sizeimage ? -EINVAL : 0; *nplanes = 1; - sizes[0] = fmt ? fmt->fmt.pix.sizeimage : sur40_video_format.sizeimage; - alloc_ctxs[0] = sur40->alloc_ctx; + sizes[0] = sur40_video_format.sizeimage; return 0; } @@ -788,7 +783,6 @@ static int sur40_vidioc_enum_fmt(struct file *file, void *priv, { if (f->index != 0) return -EINVAL; - strlcpy(f->description, "8-bit greyscale", sizeof(f->description)); f->pixelformat = V4L2_PIX_FMT_GREY; f->flags = 0; return 0; diff --git b/drivers/input/touchscreen/surface3_spi.c b/drivers/input/touchscreen/surface3_spi.c new file mode 100644 index 0000000..e12fb9b --- /dev/null +++ b/drivers/input/touchscreen/surface3_spi.c @@ -0,0 +1,427 @@ +/* + * Driver for Ntrig/Microsoft Touchscreens over SPI + * + * Copyright (c) 2016 Red Hat Inc. + */ + +/* + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SURFACE3_PACKET_SIZE 264 + +#define SURFACE3_REPORT_TOUCH 0xd2 +#define SURFACE3_REPORT_PEN 0x16 + +struct surface3_ts_data { + struct spi_device *spi; + struct gpio_desc *gpiod_rst[2]; + struct input_dev *input_dev; + struct input_dev *pen_input_dev; + int pen_tool; + + u8 rd_buf[SURFACE3_PACKET_SIZE] ____cacheline_aligned; +}; + +struct surface3_ts_data_finger { + u8 status; + __le16 tracking_id; + __le16 x; + __le16 cx; + __le16 y; + __le16 cy; + __le16 width; + __le16 height; + u32 padding; +} __packed; + +struct surface3_ts_data_pen { + u8 status; + __le16 x; + __le16 y; + __le16 pressure; + u8 padding; +} __packed; + +static int surface3_spi_read(struct surface3_ts_data *ts_data) +{ + struct spi_device *spi = ts_data->spi; + + memset(ts_data->rd_buf, 0, sizeof(ts_data->rd_buf)); + return spi_read(spi, ts_data->rd_buf, sizeof(ts_data->rd_buf)); +} + +static void surface3_spi_report_touch(struct surface3_ts_data *ts_data, + struct surface3_ts_data_finger *finger) +{ + int st = finger->status & 0x01; + int slot; + + slot = input_mt_get_slot_by_key(ts_data->input_dev, + get_unaligned_le16(&finger->tracking_id)); + if (slot < 0) + return; + + input_mt_slot(ts_data->input_dev, slot); + input_mt_report_slot_state(ts_data->input_dev, MT_TOOL_FINGER, st); + if (st) { + input_report_abs(ts_data->input_dev, + ABS_MT_POSITION_X, + get_unaligned_le16(&finger->x)); + input_report_abs(ts_data->input_dev, + ABS_MT_POSITION_Y, + get_unaligned_le16(&finger->y)); + input_report_abs(ts_data->input_dev, + ABS_MT_WIDTH_MAJOR, + get_unaligned_le16(&finger->width)); + input_report_abs(ts_data->input_dev, + ABS_MT_WIDTH_MINOR, + get_unaligned_le16(&finger->height)); + } +} + +static void surface3_spi_process_touch(struct surface3_ts_data *ts_data, u8 *data) +{ + u16 timestamp; + unsigned int i; + timestamp = get_unaligned_le16(&data[15]); + + for (i = 0; i < 13; i++) { + struct surface3_ts_data_finger *finger; + + finger = (struct surface3_ts_data_finger *)&data[17 + + i * sizeof(struct surface3_ts_data_finger)]; + + /* + * When bit 5 of status is 1, it marks the end of the report: + * - touch present: 0xe7 + * - touch released: 0xe4 + * - nothing valuable: 0xff + */ + if (finger->status & 0x10) + break; + + surface3_spi_report_touch(ts_data, finger); + } + + input_mt_sync_frame(ts_data->input_dev); + input_sync(ts_data->input_dev); +} + +static void surface3_spi_report_pen(struct surface3_ts_data *ts_data, + struct surface3_ts_data_pen *pen) +{ + struct input_dev *dev = ts_data->pen_input_dev; + int st = pen->status; + int prox = st & 0x01; + int rubber = st & 0x18; + int tool = (prox && rubber) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; + + /* fake proximity out to switch tools */ + if (ts_data->pen_tool != tool) { + input_report_key(dev, ts_data->pen_tool, 0); + input_sync(dev); + ts_data->pen_tool = tool; + } + + input_report_key(dev, BTN_TOUCH, st & 0x12); + + input_report_key(dev, ts_data->pen_tool, prox); + + if (st) { + input_report_key(dev, + BTN_STYLUS, + st & 0x04); + + input_report_abs(dev, + ABS_X, + get_unaligned_le16(&pen->x)); + input_report_abs(dev, + ABS_Y, + get_unaligned_le16(&pen->y)); + input_report_abs(dev, + ABS_PRESSURE, + get_unaligned_le16(&pen->pressure)); + } +} + +static void surface3_spi_process_pen(struct surface3_ts_data *ts_data, u8 *data) +{ + struct surface3_ts_data_pen *pen; + + pen = (struct surface3_ts_data_pen *)&data[15]; + + surface3_spi_report_pen(ts_data, pen); + input_sync(ts_data->pen_input_dev); +} + +static void surface3_spi_process(struct surface3_ts_data *ts_data) +{ + const char header[] = { + 0xff, 0xff, 0xff, 0xff, 0xa5, 0x5a, 0xe7, 0x7e, 0x01 + }; + u8 *data = ts_data->rd_buf; + + if (memcmp(header, data, sizeof(header))) + dev_err(&ts_data->spi->dev, + "%s header error: %*ph, ignoring...\n", + __func__, (int)sizeof(header), data); + + switch (data[9]) { + case SURFACE3_REPORT_TOUCH: + surface3_spi_process_touch(ts_data, data); + break; + case SURFACE3_REPORT_PEN: + surface3_spi_process_pen(ts_data, data); + break; + default: + dev_err(&ts_data->spi->dev, + "%s unknown packet type: %x, ignoring...\n", + __func__, data[9]); + break; + } +} + +static irqreturn_t surface3_spi_irq_handler(int irq, void *dev_id) +{ + struct surface3_ts_data *data = dev_id; + + if (surface3_spi_read(data)) + return IRQ_HANDLED; + + dev_dbg(&data->spi->dev, "%s received -> %*ph\n", + __func__, SURFACE3_PACKET_SIZE, data->rd_buf); + surface3_spi_process(data); + + return IRQ_HANDLED; +} + +static void surface3_spi_power(struct surface3_ts_data *data, bool on) +{ + gpiod_set_value(data->gpiod_rst[0], on); + gpiod_set_value(data->gpiod_rst[1], on); + /* let the device settle a little */ + msleep(20); +} + +/** + * surface3_spi_get_gpio_config - Get GPIO config from ACPI/DT + * + * @ts: surface3_spi_ts_data pointer + */ +static int surface3_spi_get_gpio_config(struct surface3_ts_data *data) +{ + int error; + struct device *dev; + struct gpio_desc *gpiod; + int i; + + dev = &data->spi->dev; + + /* Get the reset lines GPIO pin number */ + for (i = 0; i < 2; i++) { + gpiod = devm_gpiod_get_index(dev, NULL, i, GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) { + error = PTR_ERR(gpiod); + if (error != -EPROBE_DEFER) + dev_err(dev, + "Failed to get power GPIO %d: %d\n", + i, + error); + return error; + } + + data->gpiod_rst[i] = gpiod; + } + + return 0; +} + +static int surface3_spi_create_touch_input(struct surface3_ts_data *data) +{ + struct input_dev *input; + int error; + + input = devm_input_allocate_device(&data->spi->dev); + if (!input) + return -ENOMEM; + + data->input_dev = input; + + input_set_abs_params(input, ABS_MT_POSITION_X, 0, 9600, 0, 0); + input_abs_set_res(input, ABS_MT_POSITION_X, 40); + input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 7200, 0, 0); + input_abs_set_res(input, ABS_MT_POSITION_Y, 48); + input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 1024, 0, 0); + input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 1024, 0, 0); + input_mt_init_slots(input, 10, INPUT_MT_DIRECT); + + input->name = "Surface3 SPI Capacitive TouchScreen"; + input->phys = "input/ts"; + input->id.bustype = BUS_SPI; + input->id.vendor = 0x045e; /* Microsoft */ + input->id.product = 0x0001; + input->id.version = 0x0000; + + error = input_register_device(input); + if (error) { + dev_err(&data->spi->dev, + "Failed to register input device: %d", error); + return error; + } + + return 0; +} + +static int surface3_spi_create_pen_input(struct surface3_ts_data *data) +{ + struct input_dev *input; + int error; + + input = devm_input_allocate_device(&data->spi->dev); + if (!input) + return -ENOMEM; + + data->pen_input_dev = input; + data->pen_tool = BTN_TOOL_PEN; + + __set_bit(INPUT_PROP_DIRECT, input->propbit); + __set_bit(INPUT_PROP_POINTER, input->propbit); + input_set_abs_params(input, ABS_X, 0, 9600, 0, 0); + input_abs_set_res(input, ABS_X, 40); + input_set_abs_params(input, ABS_Y, 0, 7200, 0, 0); + input_abs_set_res(input, ABS_Y, 48); + input_set_abs_params(input, ABS_PRESSURE, 0, 1024, 0, 0); + input_set_capability(input, EV_KEY, BTN_TOUCH); + input_set_capability(input, EV_KEY, BTN_STYLUS); + input_set_capability(input, EV_KEY, BTN_TOOL_PEN); + input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER); + + input->name = "Surface3 SPI Pen Input"; + input->phys = "input/ts"; + input->id.bustype = BUS_SPI; + input->id.vendor = 0x045e; /* Microsoft */ + input->id.product = 0x0002; + input->id.version = 0x0000; + + error = input_register_device(input); + if (error) { + dev_err(&data->spi->dev, + "Failed to register input device: %d", error); + return error; + } + + return 0; +} + +static int surface3_spi_probe(struct spi_device *spi) +{ + struct surface3_ts_data *data; + int error; + + /* Set up SPI*/ + spi->bits_per_word = 8; + spi->mode = SPI_MODE_0; + error = spi_setup(spi); + if (error) + return error; + + data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->spi = spi; + spi_set_drvdata(spi, data); + + error = surface3_spi_get_gpio_config(data); + if (error) + return error; + + surface3_spi_power(data, true); + surface3_spi_power(data, false); + surface3_spi_power(data, true); + + error = surface3_spi_create_touch_input(data); + if (error) + return error; + + error = surface3_spi_create_pen_input(data); + if (error) + return error; + + error = devm_request_threaded_irq(&spi->dev, spi->irq, + NULL, surface3_spi_irq_handler, + IRQF_ONESHOT, + "Surface3-irq", data); + if (error) + return error; + + return 0; +} + +static int __maybe_unused surface3_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct surface3_ts_data *data = spi_get_drvdata(spi); + + disable_irq(data->spi->irq); + + surface3_spi_power(data, false); + + return 0; +} + +static int __maybe_unused surface3_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct surface3_ts_data *data = spi_get_drvdata(spi); + + surface3_spi_power(data, true); + + enable_irq(data->spi->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(surface3_spi_pm_ops, + surface3_spi_suspend, + surface3_spi_resume); + +#ifdef CONFIG_ACPI +static const struct acpi_device_id surface3_spi_acpi_match[] = { + { "MSHW0037", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, surface3_spi_acpi_match); +#endif + +static struct spi_driver surface3_spi_driver = { + .driver = { + .name = "Surface3-spi", + .acpi_match_table = ACPI_PTR(surface3_spi_acpi_match), + .pm = &surface3_spi_pm_ops, + }, + .probe = surface3_spi_probe, +}; + +module_spi_driver(surface3_spi_driver); + +MODULE_AUTHOR("Benjamin Tissoires "); +MODULE_DESCRIPTION("Surface 3 SPI touchscreen driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/touchscreen/ti_am335x_tsc.c b/drivers/input/touchscreen/ti_am335x_tsc.c index 191a1b8..7953381 100644 --- a/drivers/input/touchscreen/ti_am335x_tsc.c +++ b/drivers/input/touchscreen/ti_am335x_tsc.c @@ -273,8 +273,6 @@ static irqreturn_t titsc_irq(int irq, void *dev) status = titsc_readl(ts_dev, REG_RAWIRQSTATUS); if (status & IRQENB_HW_PEN) { ts_dev->pen_down = true; - titsc_writel(ts_dev, REG_IRQWAKEUP, 0x00); - titsc_writel(ts_dev, REG_IRQCLR, IRQENB_HW_PEN); irqclr |= IRQENB_HW_PEN; } @@ -408,7 +406,7 @@ static int titsc_probe(struct platform_device *pdev) int err; /* Allocate memory for device */ - ts_dev = kzalloc(sizeof(struct titsc), GFP_KERNEL); + ts_dev = kzalloc(sizeof(*ts_dev), GFP_KERNEL); input_dev = input_allocate_device(); if (!ts_dev || !input_dev) { dev_err(&pdev->dev, "failed to allocate memory.\n"); @@ -489,8 +487,7 @@ static int titsc_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM -static int titsc_suspend(struct device *dev) +static int __maybe_unused titsc_suspend(struct device *dev) { struct titsc *ts_dev = dev_get_drvdata(dev); struct ti_tscadc_dev *tscadc_dev; @@ -506,7 +503,7 @@ static int titsc_suspend(struct device *dev) return 0; } -static int titsc_resume(struct device *dev) +static int __maybe_unused titsc_resume(struct device *dev) { struct titsc *ts_dev = dev_get_drvdata(dev); struct ti_tscadc_dev *tscadc_dev; @@ -523,14 +520,7 @@ static int titsc_resume(struct device *dev) return 0; } -static const struct dev_pm_ops titsc_pm_ops = { - .suspend = titsc_suspend, - .resume = titsc_resume, -}; -#define TITSC_PM_OPS (&titsc_pm_ops) -#else -#define TITSC_PM_OPS NULL -#endif +static SIMPLE_DEV_PM_OPS(titsc_pm_ops, titsc_suspend, titsc_resume); static const struct of_device_id ti_tsc_dt_ids[] = { { .compatible = "ti,am3359-tsc", }, @@ -543,7 +533,7 @@ static struct platform_driver ti_tsc_driver = { .remove = titsc_remove, .driver = { .name = "TI-am335x-tsc", - .pm = TITSC_PM_OPS, + .pm = &titsc_pm_ops, .of_match_table = ti_tsc_dt_ids, }, }; diff --git b/drivers/input/touchscreen/ts4800-ts.c b/drivers/input/touchscreen/ts4800-ts.c new file mode 100644 index 0000000..fed73ee --- /dev/null +++ b/drivers/input/touchscreen/ts4800-ts.c @@ -0,0 +1,217 @@ +/* + * Touchscreen driver for the TS-4800 board + * + * Copyright (c) 2015 - Savoir-faire Linux + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* polling interval in ms */ +#define POLL_INTERVAL 3 + +#define DEBOUNCE_COUNT 1 + +/* sensor values are 12-bit wide */ +#define MAX_12BIT ((1 << 12) - 1) + +#define PENDOWN_MASK 0x1 + +#define X_OFFSET 0x0 +#define Y_OFFSET 0x2 + +struct ts4800_ts { + struct input_polled_dev *poll_dev; + struct device *dev; + char phys[32]; + + void __iomem *base; + struct regmap *regmap; + unsigned int reg; + unsigned int bit; + + bool pendown; + int debounce; +}; + +static void ts4800_ts_open(struct input_polled_dev *dev) +{ + struct ts4800_ts *ts = dev->private; + int ret; + + ts->pendown = false; + ts->debounce = DEBOUNCE_COUNT; + + ret = regmap_update_bits(ts->regmap, ts->reg, ts->bit, ts->bit); + if (ret) + dev_warn(ts->dev, "Failed to enable touchscreen\n"); +} + +static void ts4800_ts_close(struct input_polled_dev *dev) +{ + struct ts4800_ts *ts = dev->private; + int ret; + + ret = regmap_update_bits(ts->regmap, ts->reg, ts->bit, 0); + if (ret) + dev_warn(ts->dev, "Failed to disable touchscreen\n"); + +} + +static void ts4800_ts_poll(struct input_polled_dev *dev) +{ + struct input_dev *input_dev = dev->input; + struct ts4800_ts *ts = dev->private; + u16 last_x = readw(ts->base + X_OFFSET); + u16 last_y = readw(ts->base + Y_OFFSET); + bool pendown = last_x & PENDOWN_MASK; + + if (pendown) { + if (ts->debounce) { + ts->debounce--; + return; + } + + if (!ts->pendown) { + input_report_key(input_dev, BTN_TOUCH, 1); + ts->pendown = true; + } + + last_x = ((~last_x) >> 4) & MAX_12BIT; + last_y = ((~last_y) >> 4) & MAX_12BIT; + + input_report_abs(input_dev, ABS_X, last_x); + input_report_abs(input_dev, ABS_Y, last_y); + input_sync(input_dev); + } else if (ts->pendown) { + ts->pendown = false; + ts->debounce = DEBOUNCE_COUNT; + input_report_key(input_dev, BTN_TOUCH, 0); + input_sync(input_dev); + } +} + +static int ts4800_parse_dt(struct platform_device *pdev, + struct ts4800_ts *ts) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *syscon_np; + u32 reg, bit; + int error; + + syscon_np = of_parse_phandle(np, "syscon", 0); + if (!syscon_np) { + dev_err(dev, "no syscon property\n"); + return -ENODEV; + } + + ts->regmap = syscon_node_to_regmap(syscon_np); + of_node_put(syscon_np); + if (IS_ERR(ts->regmap)) { + dev_err(dev, "cannot get parent's regmap\n"); + return PTR_ERR(ts->regmap); + } + + error = of_property_read_u32_index(np, "syscon", 1, ®); + if (error < 0) { + dev_err(dev, "no offset in syscon\n"); + return error; + } + + ts->reg = reg; + + error = of_property_read_u32_index(np, "syscon", 2, &bit); + if (error < 0) { + dev_err(dev, "no bit in syscon\n"); + return error; + } + + ts->bit = BIT(bit); + + return 0; +} + +static int ts4800_ts_probe(struct platform_device *pdev) +{ + struct input_polled_dev *poll_dev; + struct ts4800_ts *ts; + struct resource *res; + int error; + + ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return -ENOMEM; + + error = ts4800_parse_dt(pdev, ts); + if (error) + return error; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ts->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ts->base)) + return PTR_ERR(ts->base); + + poll_dev = devm_input_allocate_polled_device(&pdev->dev); + if (!poll_dev) + return -ENOMEM; + + snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&pdev->dev)); + ts->poll_dev = poll_dev; + ts->dev = &pdev->dev; + + poll_dev->private = ts; + poll_dev->poll_interval = POLL_INTERVAL; + poll_dev->open = ts4800_ts_open; + poll_dev->close = ts4800_ts_close; + poll_dev->poll = ts4800_ts_poll; + + poll_dev->input->name = "TS-4800 Touchscreen"; + poll_dev->input->phys = ts->phys; + + input_set_capability(poll_dev->input, EV_KEY, BTN_TOUCH); + input_set_abs_params(poll_dev->input, ABS_X, 0, MAX_12BIT, 0, 0); + input_set_abs_params(poll_dev->input, ABS_Y, 0, MAX_12BIT, 0, 0); + + error = input_register_polled_device(poll_dev); + if (error) { + dev_err(&pdev->dev, + "Unabled to register polled input device (%d)\n", + error); + return error; + } + + return 0; +} + +static const struct of_device_id ts4800_ts_of_match[] = { + { .compatible = "technologic,ts4800-ts", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ts4800_ts_of_match); + +static struct platform_driver ts4800_ts_driver = { + .driver = { + .name = "ts4800-ts", + .of_match_table = ts4800_ts_of_match, + }, + .probe = ts4800_ts_probe, +}; +module_platform_driver(ts4800_ts_driver); + +MODULE_AUTHOR("Damien Riegel "); +MODULE_DESCRIPTION("TS-4800 Touchscreen Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ts4800_ts"); diff --git a/drivers/input/touchscreen/tsc200x-core.c b/drivers/input/touchscreen/tsc200x-core.c index dfa7f1c..b7059ed 100644 --- a/drivers/input/touchscreen/tsc200x-core.c +++ b/drivers/input/touchscreen/tsc200x-core.c @@ -568,7 +568,7 @@ int tsc200x_probe(struct device *dev, int irq, const struct input_id *tsc_id, input_set_abs_params(input_dev, ABS_PRESSURE, 0, max_p, fudge_p, 0); if (np) - touchscreen_parse_properties(input_dev, false); + touchscreen_parse_properties(input_dev, false, NULL); input_dev->open = tsc200x_open; input_dev->close = tsc200x_close; diff --git a/drivers/input/touchscreen/wacom_w8001.c b/drivers/input/touchscreen/wacom_w8001.c index 3ed0ce1..85e9572 100644 --- a/drivers/input/touchscreen/wacom_w8001.c +++ b/drivers/input/touchscreen/wacom_w8001.c @@ -80,7 +80,8 @@ struct w8001_touch_query { */ struct w8001 { - struct input_dev *dev; + struct input_dev *pen_dev; + struct input_dev *touch_dev; struct serio *serio; struct completion cmd_done; int id; @@ -95,7 +96,10 @@ struct w8001 { u16 max_touch_y; u16 max_pen_x; u16 max_pen_y; - char name[64]; + char pen_name[64]; + char touch_name[64]; + int open_count; + struct mutex mutex; }; static void parse_pen_data(u8 *data, struct w8001_coord *coord) @@ -141,7 +145,7 @@ static void scale_touch_coordinates(struct w8001 *w8001, static void parse_multi_touch(struct w8001 *w8001) { - struct input_dev *dev = w8001->dev; + struct input_dev *dev = w8001->touch_dev; unsigned char *data = w8001->data; unsigned int x, y; int i; @@ -207,7 +211,7 @@ static void parse_touchquery(u8 *data, struct w8001_touch_query *query) static void report_pen_events(struct w8001 *w8001, struct w8001_coord *coord) { - struct input_dev *dev = w8001->dev; + struct input_dev *dev = w8001->pen_dev; /* * We have 1 bit for proximity (rdy) and 3 bits for tip, side, @@ -233,11 +237,6 @@ static void report_pen_events(struct w8001 *w8001, struct w8001_coord *coord) break; case BTN_TOOL_FINGER: - input_report_key(dev, BTN_TOUCH, 0); - input_report_key(dev, BTN_TOOL_FINGER, 0); - input_sync(dev); - /* fall through */ - case KEY_RESERVED: w8001->type = coord->f2 ? BTN_TOOL_RUBBER : BTN_TOOL_PEN; break; @@ -261,7 +260,7 @@ static void report_pen_events(struct w8001 *w8001, struct w8001_coord *coord) static void report_single_touch(struct w8001 *w8001, struct w8001_coord *coord) { - struct input_dev *dev = w8001->dev; + struct input_dev *dev = w8001->touch_dev; unsigned int x = coord->x; unsigned int y = coord->y; @@ -271,7 +270,6 @@ static void report_single_touch(struct w8001 *w8001, struct w8001_coord *coord) input_report_abs(dev, ABS_X, x); input_report_abs(dev, ABS_Y, y); input_report_key(dev, BTN_TOUCH, coord->tsw); - input_report_key(dev, BTN_TOOL_FINGER, coord->tsw); input_sync(dev); @@ -342,6 +340,15 @@ static irqreturn_t w8001_interrupt(struct serio *serio, w8001->idx = 0; parse_multi_touch(w8001); break; + + default: + /* + * ThinkPad X60 Tablet PC (pen only device) sometimes + * sends invalid data packets that are larger than + * W8001_PKTLEN_TPCPEN. Let's start over again. + */ + if (!w8001->touch_dev && w8001->idx > W8001_PKTLEN_TPCPEN - 1) + w8001->idx = 0; } return IRQ_HANDLED; @@ -369,22 +376,36 @@ static int w8001_command(struct w8001 *w8001, unsigned char command, static int w8001_open(struct input_dev *dev) { struct w8001 *w8001 = input_get_drvdata(dev); + int err; + + err = mutex_lock_interruptible(&w8001->mutex); + if (err) + return err; + + if (w8001->open_count++ == 0) { + err = w8001_command(w8001, W8001_CMD_START, false); + if (err) + w8001->open_count--; + } - return w8001_command(w8001, W8001_CMD_START, false); + mutex_unlock(&w8001->mutex); + return err; } static void w8001_close(struct input_dev *dev) { struct w8001 *w8001 = input_get_drvdata(dev); - w8001_command(w8001, W8001_CMD_STOP, false); + mutex_lock(&w8001->mutex); + + if (--w8001->open_count == 0) + w8001_command(w8001, W8001_CMD_STOP, false); + + mutex_unlock(&w8001->mutex); } -static int w8001_setup(struct w8001 *w8001) +static int w8001_detect(struct w8001 *w8001) { - struct input_dev *dev = w8001->dev; - struct w8001_coord coord; - struct w8001_touch_query touch; int error; error = w8001_command(w8001, W8001_CMD_STOP, false); @@ -393,105 +414,155 @@ static int w8001_setup(struct w8001 *w8001) msleep(250); /* wait 250ms before querying the device */ - dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); - strlcat(w8001->name, "Wacom Serial", sizeof(w8001->name)); + return 0; +} - __set_bit(INPUT_PROP_DIRECT, dev->propbit); +static int w8001_setup_pen(struct w8001 *w8001, char *basename, + size_t basename_sz) +{ + struct input_dev *dev = w8001->pen_dev; + struct w8001_coord coord; + int error; /* penabled? */ error = w8001_command(w8001, W8001_CMD_QUERY, true); - if (!error) { - __set_bit(BTN_TOUCH, dev->keybit); - __set_bit(BTN_TOOL_PEN, dev->keybit); - __set_bit(BTN_TOOL_RUBBER, dev->keybit); - __set_bit(BTN_STYLUS, dev->keybit); - __set_bit(BTN_STYLUS2, dev->keybit); - - parse_pen_data(w8001->response, &coord); - w8001->max_pen_x = coord.x; - w8001->max_pen_y = coord.y; - - input_set_abs_params(dev, ABS_X, 0, coord.x, 0, 0); - input_set_abs_params(dev, ABS_Y, 0, coord.y, 0, 0); - input_abs_set_res(dev, ABS_X, W8001_PEN_RESOLUTION); - input_abs_set_res(dev, ABS_Y, W8001_PEN_RESOLUTION); - input_set_abs_params(dev, ABS_PRESSURE, 0, coord.pen_pressure, 0, 0); - if (coord.tilt_x && coord.tilt_y) { - input_set_abs_params(dev, ABS_TILT_X, 0, coord.tilt_x, 0, 0); - input_set_abs_params(dev, ABS_TILT_Y, 0, coord.tilt_y, 0, 0); - } - w8001->id = 0x90; - strlcat(w8001->name, " Penabled", sizeof(w8001->name)); + if (error) + return error; + + __set_bit(EV_KEY, dev->evbit); + __set_bit(EV_ABS, dev->evbit); + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(BTN_TOOL_PEN, dev->keybit); + __set_bit(BTN_TOOL_RUBBER, dev->keybit); + __set_bit(BTN_STYLUS, dev->keybit); + __set_bit(BTN_STYLUS2, dev->keybit); + __set_bit(INPUT_PROP_DIRECT, dev->propbit); + + parse_pen_data(w8001->response, &coord); + w8001->max_pen_x = coord.x; + w8001->max_pen_y = coord.y; + + input_set_abs_params(dev, ABS_X, 0, coord.x, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, coord.y, 0, 0); + input_abs_set_res(dev, ABS_X, W8001_PEN_RESOLUTION); + input_abs_set_res(dev, ABS_Y, W8001_PEN_RESOLUTION); + input_set_abs_params(dev, ABS_PRESSURE, 0, coord.pen_pressure, 0, 0); + if (coord.tilt_x && coord.tilt_y) { + input_set_abs_params(dev, ABS_TILT_X, 0, coord.tilt_x, 0, 0); + input_set_abs_params(dev, ABS_TILT_Y, 0, coord.tilt_y, 0, 0); } + w8001->id = 0x90; + strlcat(basename, " Penabled", basename_sz); + + return 0; +} + +static int w8001_setup_touch(struct w8001 *w8001, char *basename, + size_t basename_sz) +{ + struct input_dev *dev = w8001->touch_dev; + struct w8001_touch_query touch; + int error; + + /* Touch enabled? */ error = w8001_command(w8001, W8001_CMD_TOUCHQUERY, true); - + if (error) + return error; /* * Some non-touch devices may reply to the touch query. But their * second byte is empty, which indicates touch is not supported. */ - if (!error && w8001->response[1]) { - __set_bit(BTN_TOUCH, dev->keybit); - __set_bit(BTN_TOOL_FINGER, dev->keybit); - - parse_touchquery(w8001->response, &touch); - w8001->max_touch_x = touch.x; - w8001->max_touch_y = touch.y; - - if (w8001->max_pen_x && w8001->max_pen_y) { - /* if pen is supported scale to pen maximum */ - touch.x = w8001->max_pen_x; - touch.y = w8001->max_pen_y; - touch.panel_res = W8001_PEN_RESOLUTION; - } + if (!w8001->response[1]) + return -ENXIO; - input_set_abs_params(dev, ABS_X, 0, touch.x, 0, 0); - input_set_abs_params(dev, ABS_Y, 0, touch.y, 0, 0); - input_abs_set_res(dev, ABS_X, touch.panel_res); - input_abs_set_res(dev, ABS_Y, touch.panel_res); - - switch (touch.sensor_id) { - case 0: - case 2: - w8001->pktlen = W8001_PKTLEN_TOUCH93; - w8001->id = 0x93; - strlcat(w8001->name, " 1FG", sizeof(w8001->name)); - break; + __set_bit(EV_KEY, dev->evbit); + __set_bit(EV_ABS, dev->evbit); + __set_bit(BTN_TOUCH, dev->keybit); + __set_bit(INPUT_PROP_DIRECT, dev->propbit); - case 1: - case 3: - case 4: - w8001->pktlen = W8001_PKTLEN_TOUCH9A; - strlcat(w8001->name, " 1FG", sizeof(w8001->name)); - w8001->id = 0x9a; - break; + parse_touchquery(w8001->response, &touch); + w8001->max_touch_x = touch.x; + w8001->max_touch_y = touch.y; - case 5: - w8001->pktlen = W8001_PKTLEN_TOUCH2FG; - - input_mt_init_slots(dev, 2, 0); - input_set_abs_params(dev, ABS_MT_POSITION_X, - 0, touch.x, 0, 0); - input_set_abs_params(dev, ABS_MT_POSITION_Y, - 0, touch.y, 0, 0); - input_set_abs_params(dev, ABS_MT_TOOL_TYPE, - 0, MT_TOOL_MAX, 0, 0); - - strlcat(w8001->name, " 2FG", sizeof(w8001->name)); - if (w8001->max_pen_x && w8001->max_pen_y) - w8001->id = 0xE3; - else - w8001->id = 0xE2; - break; + if (w8001->max_pen_x && w8001->max_pen_y) { + /* if pen is supported scale to pen maximum */ + touch.x = w8001->max_pen_x; + touch.y = w8001->max_pen_y; + touch.panel_res = W8001_PEN_RESOLUTION; + } + + input_set_abs_params(dev, ABS_X, 0, touch.x, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, touch.y, 0, 0); + input_abs_set_res(dev, ABS_X, touch.panel_res); + input_abs_set_res(dev, ABS_Y, touch.panel_res); + + switch (touch.sensor_id) { + case 0: + case 2: + w8001->pktlen = W8001_PKTLEN_TOUCH93; + w8001->id = 0x93; + strlcat(basename, " 1FG", basename_sz); + break; + + case 1: + case 3: + case 4: + w8001->pktlen = W8001_PKTLEN_TOUCH9A; + strlcat(basename, " 1FG", basename_sz); + w8001->id = 0x9a; + break; + + case 5: + w8001->pktlen = W8001_PKTLEN_TOUCH2FG; + + __set_bit(BTN_TOOL_DOUBLETAP, dev->keybit); + error = input_mt_init_slots(dev, 2, 0); + if (error) { + dev_err(&w8001->serio->dev, + "failed to initialize MT slots: %d\n", error); + return error; } + + input_set_abs_params(dev, ABS_MT_POSITION_X, + 0, touch.x, 0, 0); + input_set_abs_params(dev, ABS_MT_POSITION_Y, + 0, touch.y, 0, 0); + input_set_abs_params(dev, ABS_MT_TOOL_TYPE, + 0, MT_TOOL_MAX, 0, 0); + input_abs_set_res(dev, ABS_MT_POSITION_X, touch.panel_res); + input_abs_set_res(dev, ABS_MT_POSITION_Y, touch.panel_res); + + strlcat(basename, " 2FG", basename_sz); + if (w8001->max_pen_x && w8001->max_pen_y) + w8001->id = 0xE3; + else + w8001->id = 0xE2; + break; } - strlcat(w8001->name, " Touchscreen", sizeof(w8001->name)); + strlcat(basename, " Touchscreen", basename_sz); return 0; } +static void w8001_set_devdata(struct input_dev *dev, struct w8001 *w8001, + struct serio *serio) +{ + dev->phys = w8001->phys; + dev->id.bustype = BUS_RS232; + dev->id.product = w8001->id; + dev->id.vendor = 0x056a; + dev->id.version = 0x0100; + dev->open = w8001_open; + dev->close = w8001_close; + + dev->dev.parent = &serio->dev; + + input_set_drvdata(dev, w8001); +} + /* * w8001_disconnect() is the opposite of w8001_connect() */ @@ -502,7 +573,10 @@ static void w8001_disconnect(struct serio *serio) serio_close(serio); - input_unregister_device(w8001->dev); + if (w8001->pen_dev) + input_unregister_device(w8001->pen_dev); + if (w8001->touch_dev) + input_unregister_device(w8001->touch_dev); kfree(w8001); serio_set_drvdata(serio, NULL); @@ -517,18 +591,23 @@ static void w8001_disconnect(struct serio *serio) static int w8001_connect(struct serio *serio, struct serio_driver *drv) { struct w8001 *w8001; - struct input_dev *input_dev; - int err; + struct input_dev *input_dev_pen; + struct input_dev *input_dev_touch; + char basename[64]; + int err, err_pen, err_touch; w8001 = kzalloc(sizeof(struct w8001), GFP_KERNEL); - input_dev = input_allocate_device(); - if (!w8001 || !input_dev) { + input_dev_pen = input_allocate_device(); + input_dev_touch = input_allocate_device(); + if (!w8001 || !input_dev_pen || !input_dev_touch) { err = -ENOMEM; goto fail1; } w8001->serio = serio; - w8001->dev = input_dev; + w8001->pen_dev = input_dev_pen; + w8001->touch_dev = input_dev_touch; + mutex_init(&w8001->mutex); init_completion(&w8001->cmd_done); snprintf(w8001->phys, sizeof(w8001->phys), "%s/input0", serio->phys); @@ -537,35 +616,67 @@ static int w8001_connect(struct serio *serio, struct serio_driver *drv) if (err) goto fail2; - err = w8001_setup(w8001); + err = w8001_detect(w8001); if (err) goto fail3; - input_dev->name = w8001->name; - input_dev->phys = w8001->phys; - input_dev->id.product = w8001->id; - input_dev->id.bustype = BUS_RS232; - input_dev->id.vendor = 0x056a; - input_dev->id.version = 0x0100; - input_dev->dev.parent = &serio->dev; + /* For backwards-compatibility we compose the basename based on + * capabilities and then just append the tool type + */ + strlcpy(basename, "Wacom Serial", sizeof(basename)); - input_dev->open = w8001_open; - input_dev->close = w8001_close; + err_pen = w8001_setup_pen(w8001, basename, sizeof(basename)); + err_touch = w8001_setup_touch(w8001, basename, sizeof(basename)); + if (err_pen && err_touch) { + err = -ENXIO; + goto fail3; + } - input_set_drvdata(input_dev, w8001); + if (!err_pen) { + strlcpy(w8001->pen_name, basename, sizeof(w8001->pen_name)); + strlcat(w8001->pen_name, " Pen", sizeof(w8001->pen_name)); + input_dev_pen->name = w8001->pen_name; - err = input_register_device(w8001->dev); - if (err) - goto fail3; + w8001_set_devdata(input_dev_pen, w8001, serio); + + err = input_register_device(w8001->pen_dev); + if (err) + goto fail3; + } else { + input_free_device(input_dev_pen); + input_dev_pen = NULL; + w8001->pen_dev = NULL; + } + + if (!err_touch) { + strlcpy(w8001->touch_name, basename, sizeof(w8001->touch_name)); + strlcat(w8001->touch_name, " Finger", + sizeof(w8001->touch_name)); + input_dev_touch->name = w8001->touch_name; + + w8001_set_devdata(input_dev_touch, w8001, serio); + + err = input_register_device(w8001->touch_dev); + if (err) + goto fail4; + } else { + input_free_device(input_dev_touch); + input_dev_touch = NULL; + w8001->touch_dev = NULL; + } return 0; +fail4: + if (w8001->pen_dev) + input_unregister_device(w8001->pen_dev); fail3: serio_close(serio); fail2: serio_set_drvdata(serio, NULL); fail1: - input_free_device(input_dev); + input_free_device(input_dev_pen); + input_free_device(input_dev_touch); kfree(w8001); return err; } diff --git a/drivers/input/touchscreen/wdt87xx_i2c.c b/drivers/input/touchscreen/wdt87xx_i2c.c index 6135303..c03f410 100644 --- a/drivers/input/touchscreen/wdt87xx_i2c.c +++ b/drivers/input/touchscreen/wdt87xx_i2c.c @@ -848,7 +848,7 @@ static int wdt87xx_do_update_firmware(struct i2c_client *client, error = wdt87xx_get_sysparam(client, &wdt->param); if (error) dev_err(&client->dev, - "failed to refresh system paramaters: %d\n", error); + "failed to refresh system parameters: %d\n", error); out: enable_irq(client->irq); mutex_unlock(&wdt->fw_mutex); diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c index d32b544..ca19130 100644 --- a/drivers/mfd/tps65217.c +++ b/drivers/mfd/tps65217.c @@ -24,8 +24,12 @@ #include #include #include +#include #include #include +#include +#include +#include #include #include @@ -162,6 +166,82 @@ static const struct of_device_id tps65217_of_match[] = { }; MODULE_DEVICE_TABLE(of, tps65217_of_match); +static irqreturn_t tps65217_irq(int irq, void *irq_data) +{ + struct tps65217 *tps = irq_data; + unsigned int int_reg = 0, status_reg = 0; + + tps65217_reg_read(tps, TPS65217_REG_INT, &int_reg); + tps65217_reg_read(tps, TPS65217_REG_STATUS, &status_reg); + if (status_reg) + dev_dbg(tps->dev, "status now: 0x%X\n", status_reg); + + if (!int_reg) + return IRQ_NONE; + + if (int_reg & TPS65217_INT_PBI) { + /* Handle push button */ + dev_dbg(tps->dev, "power button status change\n"); + input_report_key(tps->pwr_but, KEY_POWER, + status_reg & TPS65217_STATUS_PB); + input_sync(tps->pwr_but); + } + if (int_reg & TPS65217_INT_ACI) { + /* Handle AC power status change */ + dev_dbg(tps->dev, "AC power status change\n"); + /* Press KEY_POWER when AC not present */ + input_report_key(tps->pwr_but, KEY_POWER, + ~status_reg & TPS65217_STATUS_ACPWR); + input_sync(tps->pwr_but); + } + if (int_reg & TPS65217_INT_USBI) { + /* Handle USB power status change */ + dev_dbg(tps->dev, "USB power status change\n"); + } + + return IRQ_HANDLED; +} + +static int tps65217_probe_pwr_but(struct tps65217 *tps) +{ + int ret; + unsigned int int_reg; + + tps->pwr_but = devm_input_allocate_device(tps->dev); + if (!tps->pwr_but) { + dev_err(tps->dev, + "Failed to allocated pwr_but input device\n"); + return -ENOMEM; + } + + tps->pwr_but->evbit[0] = BIT_MASK(EV_KEY); + tps->pwr_but->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + tps->pwr_but->name = "tps65217_pwr_but"; + ret = input_register_device(tps->pwr_but); + if (ret) { + /* NOTE: devm managed device */ + dev_err(tps->dev, "Failed to register button device\n"); + return ret; + } + ret = devm_request_threaded_irq(tps->dev, + tps->irq, NULL, tps65217_irq, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "tps65217", tps); + if (ret != 0) { + dev_err(tps->dev, "Failed to request IRQ %d\n", tps->irq); + return ret; + } + + /* enable the power button interrupt */ + ret = tps65217_reg_read(tps, TPS65217_REG_INT, &int_reg); + if (ret < 0) { + dev_err(tps->dev, "Failed to read INT reg\n"); + return ret; + } + int_reg &= ~TPS65217_INT_PBM; + tps65217_reg_write(tps, TPS65217_REG_INT, int_reg, TPS65217_PROTECT_NONE); + return 0; +} + static int tps65217_probe(struct i2c_client *client, const struct i2c_device_id *ids) { @@ -169,10 +249,13 @@ static int tps65217_probe(struct i2c_client *client, unsigned int version; unsigned long chip_id = ids->driver_data; const struct of_device_id *match; + struct device_node *node; bool status_off = false; + int irq = -1, irq_gpio = -1; int ret; - if (client->dev.of_node) { + node = client->dev.of_node; + if (node) { match = of_match_device(tps65217_of_match, &client->dev); if (!match) { dev_err(&client->dev, @@ -180,8 +263,31 @@ static int tps65217_probe(struct i2c_client *client, return -EINVAL; } chip_id = (unsigned long)match->data; - status_off = of_property_read_bool(client->dev.of_node, + status_off = of_property_read_bool(node, "ti,pmic-shutdown-controller"); + + /* at first try to get irq via OF method */ + irq = irq_of_parse_and_map(node, 0); + if (irq <= 0) { + irq = -1; + irq_gpio = of_get_named_gpio(node, "irq-gpio", 0); + if (irq_gpio >= 0) { + /* valid gpio; convert to irq */ + ret = devm_gpio_request_one(&client->dev, + irq_gpio, GPIOF_DIR_IN, + "tps65217-gpio-irq"); + if (ret != 0) + dev_warn(&client->dev, "Failed to " + "request gpio #%d\n", irq_gpio); + irq = gpio_to_irq(irq_gpio); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to " + "convert gpio #%d to irq\n", + irq_gpio); + irq = -1; + } + } + } } if (!chip_id) { @@ -205,6 +311,18 @@ static int tps65217_probe(struct i2c_client *client, return ret; } + tps->irq = irq; + tps->irq_gpio = irq_gpio; + + /* we got an irq, request it */ + if (tps->irq >= 0) { + ret = tps65217_probe_pwr_but(tps); + if (ret < 0) { + dev_err(tps->dev, "Failed to probe pwr_but\n"); + return ret; + } + } + ret = mfd_add_devices(tps->dev, -1, tps65217s, ARRAY_SIZE(tps65217s), NULL, 0, NULL); if (ret < 0) { diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 4bf7d50..14d9a4b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -525,6 +525,36 @@ config VEXPRESS_SYSCFG bus. System Configuration interface is one of the possible means of generating transactions on this bus. +config BONE_CAPEMGR + tristate "Beaglebone cape manager" + depends on ARCH_OMAP2PLUS && OF + select EEPROM + select OF_OVERLAY + help + Say Y here to include support for automatic loading of + beaglebone capes. Select M to build as a module which + will be named bone_capemgr. + +config DEV_OVERLAYMGR + tristate "Device overlay manager" + depends on OF + select OF_OVERLAY + default n + help + Say Y here to include support for the automagical dev + overlay manager. + +config TIEQEP + tristate "EQEP Hardware quadrature encoder controller" + depends on SOC_AM33XX + select PWM_TIPWMSS + help + Driver support for the EQEP quadrature encoder controller AM33XX + TI SOC + + To compile this driver as a module, choose M here: the module + will be called tieqep. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" @@ -534,7 +564,9 @@ source "drivers/misc/altera-stapl/Kconfig" source "drivers/misc/mei/Kconfig" source "drivers/misc/vmw_vmci/Kconfig" source "drivers/misc/mic/Kconfig" +source "drivers/misc/cape_bone_argus/Kconfig" source "drivers/misc/genwqe/Kconfig" +source "drivers/misc/cape/Kconfig" source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 537d7f3..44d2a52 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -52,7 +52,12 @@ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o obj-$(CONFIG_SRAM) += sram.o obj-y += mic/ +obj-y += cape_bone_argus/ obj-$(CONFIG_GENWQE) += genwqe/ +obj-y += cape/ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ +obj-$(CONFIG_TIEQEP) += tieqep.o +obj-$(CONFIG_BONE_CAPEMGR) += bone_capemgr.o +obj-$(CONFIG_DEV_OVERLAYMGR) += devovmgr.o diff --git b/drivers/misc/bone_capemgr.c b/drivers/misc/bone_capemgr.c new file mode 100644 index 0000000..094ea75 --- /dev/null +++ b/drivers/misc/bone_capemgr.c @@ -0,0 +1,1884 @@ +/* + * TI Beaglebone cape manager + * + * Copyright (C) 2012 Texas Instruments Inc. + * Copyright (C) 2012-2015 Konsulko Group. + * Author: Pantelis Antoniou + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* disabled capes */ +static char *disable_partno; +module_param(disable_partno, charp, 0444); +MODULE_PARM_DESC(disable_partno, + "Comma delimited list of PART-NUMBER[:REV] of disabled capes"); + +/* enable capes */ +static char *enable_partno; +module_param(enable_partno, charp, 0444); +MODULE_PARM_DESC(enable_partno, + "Comma delimited list of PART-NUMBER[:REV] of enabled capes"); + +/* delay to scan on boot until rootfs appears */ +static int boot_scan_period = 1000; +module_param(boot_scan_period, int, 0444); +MODULE_PARM_DESC(boot_scan_period, + "boot scan period until rootfs firmware is available"); + +struct capemgr_info; + +struct slot_ee_attribute { + struct device_attribute devattr; + unsigned int field; + struct bone_cape_slot *slot; /* this is filled when instantiated */ +}; +#define to_slot_ee_attribute(x) \ + container_of((x), struct slot_ee_attribute, devattr) + +struct bbrd_ee_attribute { + struct device_attribute devattr; + unsigned int field; +}; +#define to_bbrd_ee_attribute(x) \ + container_of((x), struct bbrd_ee_attribute, devattr) + +struct bone_cape_slot { + struct list_head node; + struct capemgr_info *info; + int slotno; + struct nvmem_cell *nvmem_cell; + + char text_id[256]; + char signature[256]; + /* quick access */ + char board_name[32+1]; + char version[4+1]; + char manufacturer[16+1]; + char part_number[16+1]; + + /* attribute group */ + char *ee_attr_name; + int ee_attrs_count; + struct slot_ee_attribute *ee_attrs; + struct attribute **ee_attrs_tab; + struct attribute_group attrgroup; + + /* state flags */ + unsigned int probed : 1; + unsigned int probe_failed : 1; + unsigned int override : 1; + unsigned int loading : 1; + unsigned int loaded : 1; + unsigned int retry_loading : 1; + unsigned int disabled : 1; + + char *dtbo; + const struct firmware *fw; + struct device_node *overlay; + int overlay_id; + + /* loader thread */ + struct task_struct *loader_thread; + + /* load priority */ + int priority; +}; + +struct bone_baseboard { + + /* from the matched boardmap node */ + char *compatible_name; + + /* filled in by reading the eeprom */ + char signature[256]; + char text_id[64+1]; + + /* quick access */ + char board_name[8+1]; + char revision[4+1]; + char serial_number[12+1]; + + /* access to the eeprom */ + struct nvmem_cell *nvmem_cell; +}; + +struct capemgr_info { + struct platform_device *pdev; + + atomic_t next_slot_nr; + struct list_head slot_list; + struct mutex slots_list_mutex; + + /* baseboard EEPROM data */ + struct bone_baseboard baseboard; + + /* wait queue for keeping the priorities straight */ + wait_queue_head_t load_wq; +}; + +static int bone_slot_fill_override(struct bone_cape_slot *slot, + const char *part_number, const char *version); +static struct bone_cape_slot *capemgr_add_slot( + struct capemgr_info *info, const char *slot_name, + const char *part_number, const char *version, int prio); +static int capemgr_remove_slot_no_lock(struct bone_cape_slot *slot); +static int capemgr_remove_slot(struct bone_cape_slot *slot); +static int capemgr_load_slot(struct bone_cape_slot *slot); +static int capemgr_unload_slot(struct bone_cape_slot *slot); + +/* baseboard EEPROM field definition */ +#define BBRD_EE_FIELD_HEADER 0 +#define BBRD_EE_FIELD_BOARD_NAME 1 +#define BBRD_EE_FIELD_REVISION 2 +#define BBRD_EE_FIELD_SERIAL_NUMBER 3 +#define BBRD_EE_FIELD_CONFIG_OPTION 4 +#define BBRD_EE_FILED_RSVD1 5 +#define BBRD_EE_FILED_RSVD2 6 +#define BBRD_EE_FILED_RSVD3 7 + +/* cape EEPROM field definitions */ +#define CAPE_EE_FIELD_HEADER 0 +#define CAPE_EE_FIELD_EEPROM_REV 1 +#define CAPE_EE_FIELD_BOARD_NAME 2 +#define CAPE_EE_FIELD_VERSION 3 +#define CAPE_EE_FIELD_MANUFACTURER 4 +#define CAPE_EE_FIELD_PART_NUMBER 5 +#define CAPE_EE_FIELD_NUMBER_OF_PINS 6 +#define CAPE_EE_FIELD_SERIAL_NUMBER 7 +#define CAPE_EE_FIELD_PIN_USAGE 8 +#define CAPE_EE_FIELD_VDD_3V3EXP 9 +#define CAPE_EE_FIELD_VDD_5V 10 +#define CAPE_EE_FIELD_SYS_5V 11 +#define CAPE_EE_FIELD_DC_SUPPLIED 12 +#define CAPE_EE_FIELD_FIELDS_NR 13 + +#define EE_FIELD_MAKE_HEADER(p) \ + ({ \ + const u8 *_p = (p); \ + (((u32)_p[0] << 24) | ((u32)_p[1] << 16) | \ + ((u32)_p[2] << 8) | (u32)_p[3]); \ + }) + +#define EE_FIELD_HEADER_VALID 0xaa5533ee + +struct ee_field { + const char *name; + int start; + int size; + unsigned int ascii : 1; + unsigned int strip_trailing_dots : 1; + const char *override; +}; + +/* baseboard EEPROM definitions */ +static const struct ee_field bbrd_sig_fields[] = { + [BBRD_EE_FIELD_HEADER] = { + .name = "header", + .start = 0, + .size = 4, + .ascii = 0, + .override = "\xaa\x55\x33\xee", /* AA 55 33 EE */ + }, + [BBRD_EE_FIELD_BOARD_NAME] = { + .name = "board-name", + .start = 4, + .size = 8, + .ascii = 1, + .strip_trailing_dots = 1, + .override = "Board Name", + }, + [BBRD_EE_FIELD_REVISION] = { + .name = "revision", + .start = 12, + .size = 4, + .ascii = 1, + .override = "00A0", + }, + [BBRD_EE_FIELD_SERIAL_NUMBER] = { + .name = "serial-number", + .start = 16, + .size = 12, + .ascii = 1, + .override = "0000000000", + }, + [BBRD_EE_FIELD_CONFIG_OPTION] = { + .name = "config-option", + .start = 28, + .size = 32, + }, +}; + +/* cape EEPROM definitions */ +static const struct ee_field cape_sig_fields[] = { + [CAPE_EE_FIELD_HEADER] = { + .name = "header", + .start = 0, + .size = 4, + .ascii = 0, + .override = "\xaa\x55\x33\xee", /* AA 55 33 EE */ + }, + [CAPE_EE_FIELD_EEPROM_REV] = { + .name = "eeprom-format-revision", + .start = 4, + .size = 2, + .ascii = 1, + .override = "A0", + }, + [CAPE_EE_FIELD_BOARD_NAME] = { + .name = "board-name", + .start = 6, + .size = 32, + .ascii = 1, + .strip_trailing_dots = 1, + .override = "Override Board Name", + }, + [CAPE_EE_FIELD_VERSION] = { + .name = "version", + .start = 38, + .size = 4, + .ascii = 1, + .override = "00A0", + }, + [CAPE_EE_FIELD_MANUFACTURER] = { + .name = "manufacturer", + .start = 42, + .size = 16, + .ascii = 1, + .strip_trailing_dots = 1, + .override = "Override Manuf", + }, + [CAPE_EE_FIELD_PART_NUMBER] = { + .name = "part-number", + .start = 58, + .size = 16, + .ascii = 1, + .strip_trailing_dots = 1, + .override = "Override Part#", + }, + [CAPE_EE_FIELD_NUMBER_OF_PINS] = { + .name = "number-of-pins", + .start = 74, + .size = 2, + .ascii = 0, + .override = NULL, + }, + [CAPE_EE_FIELD_SERIAL_NUMBER] = { + .name = "serial-number", + .start = 76, + .size = 12, + .ascii = 1, + .override = "0000000000", + }, + [CAPE_EE_FIELD_PIN_USAGE] = { + .name = "pin-usage", + .start = 88, + .size = 140, + .ascii = 0, + .override = NULL, + }, + [CAPE_EE_FIELD_VDD_3V3EXP] = { + .name = "vdd-3v3exp", + .start = 228, + .size = 2, + .ascii = 0, + .override = NULL, + }, + [CAPE_EE_FIELD_VDD_5V] = { + .name = "vdd-5v", + .start = 230, + .size = 2, + .ascii = 0, + .override = NULL, + }, + [CAPE_EE_FIELD_SYS_5V] = { + .name = "sys-5v", + .start = 232, + .size = 2, + .ascii = 0, + .override = NULL, + }, + [CAPE_EE_FIELD_DC_SUPPLIED] = { + .name = "dc-supplied", + .start = 234, + .size = 2, + .ascii = 0, + .override = NULL, + }, +}; + +static char *ee_field_get(const struct ee_field *sig_field, + const void *data, int field, char *buf, int bufsz) +{ + int len; + + /* enough space? */ + if (bufsz < sig_field->size + sig_field->ascii) + return NULL; + + memcpy(buf, (char *)data + sig_field->start, sig_field->size); + + /* terminate ascii field */ + if (sig_field->ascii) + buf[sig_field->size] = '\0'; + + if (sig_field->strip_trailing_dots) { + len = strlen(buf); + while (len > 1 && buf[len - 1] == '.') + buf[--len] = '\0'; + } + + return buf; +} + +char *bbrd_ee_field_get(const void *data, + int field, char *buf, int bufsz) +{ + if ((unsigned int)field >= ARRAY_SIZE(bbrd_sig_fields)) + return NULL; + + return ee_field_get(&bbrd_sig_fields[field], data, field, buf, bufsz); +} + +char *cape_ee_field_get(const void *data, + int field, char *buf, int bufsz) +{ + if ((unsigned int)field >= ARRAY_SIZE(cape_sig_fields)) + return NULL; + + return ee_field_get(&cape_sig_fields[field], data, field, buf, bufsz); +} + +#ifdef CONFIG_OF +static const struct of_device_id capemgr_of_match[] = { + { + .compatible = "ti,bone-capemgr", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, capemgr_of_match); + +#endif + +static int bone_baseboard_scan(struct bone_baseboard *bbrd) +{ + struct capemgr_info *info = container_of(bbrd, + struct capemgr_info, baseboard); + const u8 *p; + int ret; + size_t len; + + p = nvmem_cell_read(bbrd->nvmem_cell, &len); + if (IS_ERR(p)) { + ret = PTR_ERR(p); + dev_err(&info->pdev->dev, + "Cannot read cell (ret=%d)\n", ret); + return ret; + } + if (len < sizeof(bbrd->signature)) { + dev_info(&info->pdev->dev, + "Short read %d (should be >= %d bytes)\n", + len, sizeof(bbrd->signature)); + return -EINVAL; + } + memcpy(bbrd->signature, p, sizeof(bbrd->signature)); + + p = bbrd->signature; + if (EE_FIELD_MAKE_HEADER(p) != EE_FIELD_HEADER_VALID) { + dev_err(&info->pdev->dev, "Invalid board signature '%08x'\n", + EE_FIELD_MAKE_HEADER(p)); + return -ENODEV; + } + + bbrd_ee_field_get(bbrd->signature, + BBRD_EE_FIELD_BOARD_NAME, + bbrd->board_name, sizeof(bbrd->board_name)); + bbrd_ee_field_get(bbrd->signature, + BBRD_EE_FIELD_REVISION, + bbrd->revision, sizeof(bbrd->revision)); + bbrd_ee_field_get(bbrd->signature, + BBRD_EE_FIELD_SERIAL_NUMBER, + bbrd->serial_number, sizeof(bbrd->serial_number)); + + /* board_name,version,manufacturer,part_number */ + snprintf(bbrd->text_id, sizeof(bbrd->text_id) - 1, + "%s,%s,%s", bbrd->board_name, bbrd->revision, + bbrd->serial_number); + + /* terminate always */ + bbrd->text_id[sizeof(bbrd->text_id) - 1] = '\0'; + + return 0; +} + +static int bone_slot_scan(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + const u8 *p; + int r; + ssize_t len; + + /* need to read EEPROM? */ + if (slot->probed) + goto slot_fail_check; + + slot->probed = 1; + + if (!slot->override) { + + p = nvmem_cell_read(slot->nvmem_cell, &len); + if (IS_ERR(p)) { + r = PTR_ERR(p); + slot->probe_failed = 1; + + /* timeout is normal when no cape is present */ + if (r != -ETIMEDOUT) + dev_err(&info->pdev->dev, + "Cannot read cell (ret=%d)\n", r); + return r; + } + if (len < sizeof(slot->signature)) { + dev_info(&info->pdev->dev, + "Short read %d (should be >= %d bytes)\n", + len, sizeof(slot->signature)); + return -EINVAL; + } + memcpy(slot->signature, p, sizeof(slot->signature)); + + } else + dev_info(&info->pdev->dev, + "Using override eeprom data at slot %d\n", + slot->slotno); + + p = slot->signature; + if (EE_FIELD_MAKE_HEADER(p) != EE_FIELD_HEADER_VALID) { + dev_err(&info->pdev->dev, + "Invalid signature '%08x' at slot %d\n", + EE_FIELD_MAKE_HEADER(p), slot->slotno); + slot->probe_failed = 1; + return -ENODEV; + } + + cape_ee_field_get(slot->signature, + CAPE_EE_FIELD_BOARD_NAME, + slot->board_name, sizeof(slot->board_name)); + cape_ee_field_get(slot->signature, + CAPE_EE_FIELD_VERSION, + slot->version, sizeof(slot->version)); + cape_ee_field_get(slot->signature, + CAPE_EE_FIELD_MANUFACTURER, + slot->manufacturer, sizeof(slot->manufacturer)); + cape_ee_field_get(slot->signature, + CAPE_EE_FIELD_PART_NUMBER, + slot->part_number, sizeof(slot->part_number)); + + /* board_name,version,manufacturer,part_number */ + snprintf(slot->text_id, sizeof(slot->text_id) - 1, + "%s,%s,%s,%s", slot->board_name, slot->version, + slot->manufacturer, slot->part_number); + + /* terminate always */ + slot->text_id[sizeof(slot->text_id) - 1] = '\0'; + +slot_fail_check: + /* slot has failed and we don't support hotpluging */ + if (slot->probe_failed) + return -ENODEV; + + return 0; +} + +/* return 0 if not matched,, 1 if matched */ +static int bone_match_cape(const char *match, + const char *part_number, const char *version) +{ + char *tmp_part_number, *tmp_version; + char *buf, *s, *e, *sn; + int found; + + if (match == NULL || part_number == NULL) + return 0; + + /* copy the argument to work on it */ + buf = kstrdup(match, GFP_KERNEL); + + /* no memory, too bad... */ + if (buf == NULL) + return 0; + + found = 0; + s = buf; + e = s + strlen(s); + while (s < e) { + /* find comma separator */ + sn = strchr(s, ','); + if (sn != NULL) + *sn++ = '\0'; + else + sn = e; + tmp_part_number = s; + tmp_version = strchr(tmp_part_number, ':'); + if (tmp_version != NULL) + *tmp_version++ = '\0'; + s = sn; + + /* the part names must match */ + if (strcmp(tmp_part_number, part_number) != 0) + continue; + + /* if there's no version, match any */ + if (version == NULL || tmp_version == NULL || + strcmp(version, tmp_version) == 0) { + found = 1; + break; + } + } + + kfree(buf); + + return found; +} + +/* helper method */ +static int of_multi_prop_cmp(const struct property *prop, const char *value) +{ + const char *cp; + int cplen, vlen, l; + + /* check if it's directly compatible */ + cp = prop->value; + cplen = prop->length; + vlen = strlen(value); + + while (cplen > 0) { + /* compatible? */ + if (of_compat_cmp(cp, value, vlen) == 0) + return 0; + l = strlen(cp) + 1; + cp += l; + cplen -= l; + } + return -1; +} + +static ssize_t slot_ee_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct slot_ee_attribute *ee_attr = to_slot_ee_attribute(attr); + struct bone_cape_slot *slot = ee_attr->slot; + const struct ee_field *sig_field; + int i, len; + char *p, *s; + u16 val; + + /* add newline for ascii fields */ + sig_field = &cape_sig_fields[ee_attr->field]; + + len = sig_field->size + sig_field->ascii; + p = kmalloc(len, GFP_KERNEL); + if (p == NULL) + return -ENOMEM; + + s = cape_ee_field_get(slot->signature, ee_attr->field, p, len); + if (s == NULL) + return -EINVAL; + + /* add newline for ascii fields and return */ + if (sig_field->ascii) { + len = sprintf(buf, "%s\n", s); + goto out; + } + + /* case by case handling */ + switch (ee_attr->field) { + case CAPE_EE_FIELD_HEADER: + len = sprintf(buf, "%02x %02x %02x %02x\n", + s[0], s[1], s[2], s[3]); + break; + + /* 2 bytes */ + case CAPE_EE_FIELD_NUMBER_OF_PINS: + case CAPE_EE_FIELD_VDD_3V3EXP: + case CAPE_EE_FIELD_VDD_5V: + case CAPE_EE_FIELD_SYS_5V: + case CAPE_EE_FIELD_DC_SUPPLIED: + /* the bone is LE */ + val = s[0] & (s[1] << 8); + len = sprintf(buf, "%u\n", (unsigned int)val & 0xffff); + break; + + case CAPE_EE_FIELD_PIN_USAGE: + + len = 0; + for (i = 0; i < sig_field->size / 2; i++) { + /* the bone is LE */ + val = s[0] & (s[1] << 8); + sprintf(buf, "%04x\n", val); + buf += 5; + len += 5; + s += 2; + } + + break; + + default: + *buf = '\0'; + len = 0; + break; + } + +out: + kfree(p); + + return len; +} + +#define SLOT_EE_ATTR(_name, _field) \ + { \ + .devattr = __ATTR(_name, S_IRUGO, slot_ee_attr_show, NULL), \ + .field = CAPE_EE_FIELD_##_field, \ + .slot = NULL, \ + } + +static const struct slot_ee_attribute slot_ee_attrs[] = { + SLOT_EE_ATTR(header, HEADER), + SLOT_EE_ATTR(eeprom-format-revision, EEPROM_REV), + SLOT_EE_ATTR(board-name, BOARD_NAME), + SLOT_EE_ATTR(version, VERSION), + SLOT_EE_ATTR(manufacturer, MANUFACTURER), + SLOT_EE_ATTR(part-number, PART_NUMBER), + SLOT_EE_ATTR(number-of-pins, NUMBER_OF_PINS), + SLOT_EE_ATTR(serial-number, SERIAL_NUMBER), + SLOT_EE_ATTR(pin-usage, PIN_USAGE), + SLOT_EE_ATTR(vdd-3v3exp, VDD_3V3EXP), + SLOT_EE_ATTR(vdd-5v, VDD_5V), + SLOT_EE_ATTR(sys-5v, SYS_5V), + SLOT_EE_ATTR(dc-supplied, DC_SUPPLIED), +}; + +static int bone_cape_slot_sysfs_register(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + struct slot_ee_attribute *ee_attr; + struct attribute_group *attrgroup; + int i, err, sz; + + slot->ee_attr_name = kasprintf(GFP_KERNEL, "slot-%d", slot->slotno); + if (slot->ee_attr_name == NULL) { + dev_err(dev, "slot #%d: Failed to allocate ee_attr_name\n", + slot->slotno); + err = -ENOMEM; + goto err_fail_no_ee_attr_name; + } + + slot->ee_attrs_count = ARRAY_SIZE(slot_ee_attrs); + + sz = slot->ee_attrs_count * sizeof(*slot->ee_attrs); + slot->ee_attrs = kmalloc(sz, GFP_KERNEL); + if (slot->ee_attrs == NULL) { + dev_err(dev, "slot #%d: Failed to allocate ee_attrs\n", + slot->slotno); + err = -ENOMEM; + goto err_fail_no_ee_attrs; + } + + attrgroup = &slot->attrgroup; + memset(attrgroup, 0, sizeof(*attrgroup)); + attrgroup->name = slot->ee_attr_name; + + sz = sizeof(*slot->ee_attrs_tab) * (slot->ee_attrs_count + 1); + attrgroup->attrs = kmalloc(sz, GFP_KERNEL); + if (attrgroup->attrs == NULL) { + dev_err(dev, "slot #%d: Failed to allocate ee_attrs_tab\n", + slot->slotno); + err = -ENOMEM; + goto err_fail_no_ee_attrs_tab; + } + /* copy everything over */ + memcpy(slot->ee_attrs, slot_ee_attrs, sizeof(slot_ee_attrs)); + + /* bind this attr to the slot */ + for (i = 0; i < slot->ee_attrs_count; i++) { + ee_attr = &slot->ee_attrs[i]; + ee_attr->slot = slot; + attrgroup->attrs[i] = &ee_attr->devattr.attr; + } + attrgroup->attrs[i] = NULL; + + /* make lockdep happy */ + for (i = 0; i < slot->ee_attrs_count; i++) { + ee_attr = &slot->ee_attrs[i]; + sysfs_attr_init(&ee_attr->devattr.attr); + } + + err = sysfs_create_group(&dev->kobj, attrgroup); + if (err != 0) { + dev_err(dev, "slot #%d: Failed to allocate ee_attrs_tab\n", + slot->slotno); + err = -ENOMEM; + goto err_fail_no_ee_attrs_group; + } + + return 0; + +err_fail_no_ee_attrs_group: + kfree(slot->ee_attrs_tab); +err_fail_no_ee_attrs_tab: + kfree(slot->ee_attrs); +err_fail_no_ee_attrs: + kfree(slot->ee_attr_name); +err_fail_no_ee_attr_name: + return err; +} + +static void bone_cape_slot_sysfs_unregister(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + + sysfs_remove_group(&dev->kobj, &slot->attrgroup); + kfree(slot->ee_attrs_tab); + kfree(slot->ee_attrs); + kfree(slot->ee_attr_name); +} + +static ssize_t bbrd_ee_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct bbrd_ee_attribute *ee_attr = to_bbrd_ee_attribute(attr); + struct platform_device *pdev = to_platform_device(dev); + struct capemgr_info *info = platform_get_drvdata(pdev); + struct bone_baseboard *bbrd = &info->baseboard; + const struct ee_field *sig_field; + u16 val; + int i, len; + char *p, *s; + + /* add newline for ascii fields */ + sig_field = &bbrd_sig_fields[ee_attr->field]; + + len = sig_field->size + sig_field->ascii; + p = kmalloc(len, GFP_KERNEL); + if (p == NULL) + return -ENOMEM; + + s = bbrd_ee_field_get(bbrd->signature, ee_attr->field, p, len); + if (s == NULL) + return -EINVAL; + + /* add newline for ascii fields and return */ + if (sig_field->ascii) { + len = sprintf(buf, "%s\n", s); + goto out; + } + + /* case by case handling */ + switch (ee_attr->field) { + case BBRD_EE_FIELD_HEADER: + len = sprintf(buf, "%02x %02x %02x %02x\n", + s[0], s[1], s[2], s[3]); + break; + + case BBRD_EE_FIELD_CONFIG_OPTION: + len = 0; + for (i = 0; i < sig_field->size / 2; i++) { + /* the bone is LE */ + val = s[0] & (s[1] << 8); + sprintf(buf, "%04x\n", val); + buf += 5; + len += 5; + s += 2; + } + break; + + default: + *buf = '\0'; + len = 0; + break; + } + +out: + kfree(p); + + return len; +} + +#define BBRD_EE_ATTR(_name, _field) \ + { \ + .devattr = __ATTR(_name, 0440, bbrd_ee_attr_show, NULL), \ + .field = BBRD_EE_FIELD_##_field, \ + } + +static struct bbrd_ee_attribute bbrd_ee_attrs[] = { + BBRD_EE_ATTR(header, HEADER), + BBRD_EE_ATTR(board-name, BOARD_NAME), + BBRD_EE_ATTR(revision, REVISION), + BBRD_EE_ATTR(serial-number, SERIAL_NUMBER), + BBRD_EE_ATTR(config-option, CONFIG_OPTION), +}; + +static struct attribute *bbrd_attrs_flat[] = { + &bbrd_ee_attrs[BBRD_EE_FIELD_HEADER].devattr.attr, + &bbrd_ee_attrs[BBRD_EE_FIELD_BOARD_NAME].devattr.attr, + &bbrd_ee_attrs[BBRD_EE_FIELD_REVISION].devattr.attr, + &bbrd_ee_attrs[BBRD_EE_FIELD_SERIAL_NUMBER].devattr.attr, + &bbrd_ee_attrs[BBRD_EE_FIELD_CONFIG_OPTION].devattr.attr, + NULL, +}; + +static const struct attribute_group bbrd_attr_group = { + .name = "baseboard", + .attrs = bbrd_attrs_flat, +}; + +static ssize_t slots_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t slots_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); + +static DEVICE_ATTR(slots, 0644, slots_show, slots_store); + +static struct attribute *root_attrs_flat[] = { + &dev_attr_slots.attr, + NULL, +}; + +static const struct attribute_group root_attr_group = { + .attrs = root_attrs_flat, +}; + +static const struct attribute_group *attr_groups[] = { + &root_attr_group, + &bbrd_attr_group, + NULL, +}; + +static ssize_t slots_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct capemgr_info *info = platform_get_drvdata(pdev); + struct bone_cape_slot *slot; + ssize_t len, sz; + + mutex_lock(&info->slots_list_mutex); + sz = 0; + list_for_each_entry(slot, &info->slot_list, node) { + + len = sprintf(buf, "%2d: %c%c%c%c%c%c %3d %s\n", + slot->slotno, + slot->probed ? 'P' : '-', + slot->probe_failed ? 'F' : '-', + slot->override ? 'O' : '-', + slot->loading ? 'l' : '-', + slot->loaded ? 'L' : '-', + slot->disabled ? 'D' : '-', + slot->overlay_id, slot->text_id); + + buf += len; + sz += len; + } + mutex_unlock(&info->slots_list_mutex); + + return sz; +} + +static ssize_t slots_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct capemgr_info *info = platform_get_drvdata(pdev); + struct bone_cape_slot *slot; + struct device_node *pnode, *node; + char *s, *part_number, *version; + int ret; + int slotno; + + /* check for remove slot */ + if (strlen(buf) > 0 && buf[0] == '-') { + ret = kstrtoint(buf + 1, 10, &slotno); + if (ret != 0) + return ret; + + /* now load each (take lock to be sure */ + mutex_lock(&info->slots_list_mutex); + list_for_each_entry(slot, &info->slot_list, node) { + if (slotno == slot->slotno) + goto found; + } + + mutex_unlock(&info->slots_list_mutex); + return -ENODEV; +found: + /* the hardware slots just get unloaded */ + if (!slot->override) { + ret = capemgr_unload_slot(slot); + if (ret == 0) + dev_info(&pdev->dev, + "Unloaded slot #%d\n", slotno); + else + dev_err(&pdev->dev, + "Failed to unload slot #%d\n", slotno); + } else { + ret = capemgr_remove_slot_no_lock(slot); + if (ret == 0) + dev_info(&pdev->dev, + "Removed slot #%d\n", slotno); + else + dev_err(&pdev->dev, + "Failed to remove slot #%d\n", slotno); + } + mutex_unlock(&info->slots_list_mutex); + + return ret == 0 ? strlen(buf) : ret; + } + + part_number = kstrdup(buf, GFP_KERNEL); + if (part_number == NULL) + return -ENOMEM; + + /* remove trailing spaces dots and newlines */ + s = part_number + strlen(part_number); + while (s > part_number && + (isspace(s[-1]) || s[-1] == '\n' || s[-1] == '.')) + *--s = '\0'; + + version = strchr(part_number, ':'); + if (version != NULL) + *version++ = '\0'; + + dev_info(&pdev->dev, "part_number '%s', version '%s'\n", + part_number, version ? version : "N/A"); + + pnode = pdev->dev.of_node; + node = NULL; + slot = NULL; + ret = 0; + + /* no specific slot found, try immediate */ + slot = capemgr_add_slot(info, NULL, part_number, version, 0); + + if (IS_ERR_OR_NULL(slot)) { + dev_err(&pdev->dev, "Failed to add slot #%d\n", + atomic_read(&info->next_slot_nr) - 1); + ret = slot ? PTR_ERR(slot) : -ENODEV; + slot = NULL; + goto err_fail; + } + + kfree(part_number); + + ret = capemgr_load_slot(slot); + if (ret != 0) + capemgr_remove_slot(slot); + + return ret == 0 ? strlen(buf) : ret; +err_fail: + of_node_put(node); + kfree(part_number); + return ret; +} + +/* verify the overlay */ +static int capemgr_verify_overlay(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + struct bone_baseboard *bbrd = &info->baseboard; + struct device_node *node = slot->overlay; + struct property *prop; + struct bone_cape_slot *slotn; + int err, counta, countb, i, j; + const char *ra, *rb; + + /* validate */ + if (node == NULL) { + dev_err(dev, "slot #%d: No overlay for '%s'\n", + slot->slotno, slot->part_number); + return -EINVAL; + } + + /* check if the slot is compatible with the board */ + prop = of_find_property(node, "compatible", NULL); + + /* no compatible property? */ + if (prop == NULL) { + dev_err(dev, "slot #%d: No compatible property for '%s'\n", + slot->slotno, slot->part_number); + return -EINVAL; + } + + /* verify that the cape is baseboard compatible */ + if (of_multi_prop_cmp(prop, bbrd->compatible_name) != 0) { + dev_err(dev, "slot #%d: Incompatible with baseboard for '%s'\n", + slot->slotno, slot->part_number); + return -EINVAL; + } + + /* count the strings */ + counta = of_property_count_strings(node, "exclusive-use"); + /* no valid property, or no resources; no matter, it's OK */ + if (counta <= 0) + return 0; + + /* and now check if there's a resource conflict */ + err = 0; + mutex_lock(&info->slots_list_mutex); + for (i = 0; i < counta; i++) { + + ra = NULL; + err = of_property_read_string_index(node, "exclusive-use", + i, &ra); + if (err != 0) { + dev_err(dev, "slot #%d: Could not read string #%d\n", + slot->slotno, i); + break; + } + + list_for_each_entry(slotn, &info->slot_list, node) { + + /* don't check against self */ + if (slot == slotn) + continue; + + /* only check against loaded or loading slots */ + if (!slotn->loaded && !slotn->loading) + continue; + + countb = of_property_count_strings(slotn->overlay, + "exclusive-use"); + /* no valid property, or resources; it's OK */ + if (countb <= 0) + continue; + + + for (j = 0; j < countb; j++) { + + /* count the resources */ + rb = NULL; + err = of_property_read_string_index( + slotn->overlay, "exclusive-use", + j, &rb); + if (err != 0) { + /* error, but we don't care */ + err = 0; + break; + } + + /* ignore case; just in case ;) */ + if (strcasecmp(ra, rb) == 0) { + + /* resource conflict */ + err = -EEXIST; + dev_err(dev, + "slot #%d: %s conflict %s (#%d:%s)\n", + slot->slotno, + slot->part_number, ra, + slotn->slotno, + slotn->part_number); + goto out; + } + } + } + } +out: + mutex_unlock(&info->slots_list_mutex); + + return err; +} + +static int capemgr_load_slot(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + const char *dtbo; + int err; + + if (slot->probe_failed) { + dev_err(dev, "slot #%d: probe failed for '%s'\n", + slot->slotno, slot->part_number); + return -ENODEV; + } + + if (slot->loaded) { + dev_err(dev, "slot #%d: already loaded for '%s'\n", + slot->slotno, slot->part_number); + return -EAGAIN; + } + + /* make sure we don't leak this on repeated calls */ + kfree(slot->dtbo); + slot->dtbo = NULL; + + dev_dbg(dev, "slot #%d: Requesting part number/version based '%s-%s.dtbo\n", + slot->slotno, slot->part_number, slot->version); + + /* request the part number + .dtbo*/ + slot->dtbo = kasprintf(GFP_KERNEL, "%s-%s.dtbo", + slot->part_number, slot->version); + if (slot->dtbo == NULL) { + dev_err(dev, "slot #%d: Failed to get dtbo '%s'\n", + slot->slotno, dtbo); + return -ENOMEM; + } + + dev_dbg(dev, "slot #%d: Requesting firmware '%s' for board-name '%s', version '%s'%s\n", + slot->slotno, + slot->dtbo, slot->board_name, slot->version, + system_state == SYSTEM_BOOTING ? " - booting" : ""); + + err = request_firmware_direct(&slot->fw, slot->dtbo, dev); + if (err != 0) { + dev_dbg(dev, "failed to load firmware '%s'\n", slot->dtbo); + goto err_fail_no_fw; + } + + dev_dbg(dev, "slot #%d: dtbo '%s' loaded; converting to live tree\n", + slot->slotno, slot->dtbo); + + of_fdt_unflatten_tree((unsigned long *)slot->fw->data, &slot->overlay); + if (slot->overlay == NULL) { + dev_err(dev, "slot #%d: Failed to unflatten\n", + slot->slotno); + err = -EINVAL; + goto err_fail; + } + + /* mark it as detached */ + of_node_set_flag(slot->overlay, OF_DETACHED); + + /* perform resolution */ + err = of_resolve_phandles(slot->overlay); + if (err != 0) { + dev_err(dev, "slot #%d: Failed to resolve tree\n", + slot->slotno); + goto err_fail; + } + + err = capemgr_verify_overlay(slot); + if (err != 0) { + dev_err(dev, "slot #%d: Failed verification\n", + slot->slotno); + goto err_fail; + } + + err = of_overlay_create(slot->overlay); + if (err < 0) { + dev_err(dev, "slot #%d: Failed to create overlay\n", + slot->slotno); + goto err_fail; + } + slot->overlay_id = err; + + slot->loading = 0; + slot->loaded = 1; + + dev_info(dev, "slot #%d: dtbo '%s' loaded; overlay id #%d\n", + slot->slotno, slot->dtbo, slot->overlay_id); + + return 0; + +err_fail: + + /* TODO: free the overlay, we can't right now cause + * the unflatten method does not track it */ + slot->overlay = NULL; + + release_firmware(slot->fw); + slot->fw = NULL; + +err_fail_no_fw: + slot->loading = 0; + return err; +} + +static int capemgr_unload_slot(struct bone_cape_slot *slot) +{ + if (!slot->loaded || slot->overlay_id == -1) + return -EINVAL; + + of_overlay_destroy(slot->overlay_id); + slot->overlay_id = -1; + + slot->loaded = 0; + + return 0; + +} + +/* slots_list_mutex must be taken */ +static int capemgr_remove_slot_no_lock(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + int ret; + + if (slot == NULL) + return 0; + + if (slot->loaded && slot->overlay_id >= 0) { + /* unload just in case */ + ret = capemgr_unload_slot(slot); + if (ret != 0) { + dev_err(dev, "Unable to unload slot #%d\n", + slot->slotno); + return ret; + } + } + + /* if probed OK, remove the sysfs nodes */ + if (slot->probed && !slot->probe_failed) + bone_cape_slot_sysfs_unregister(slot); + + /* remove it from the list */ + list_del(&slot->node); + + if (slot->nvmem_cell) + nvmem_cell_put(slot->nvmem_cell); + devm_kfree(dev, slot); + return 0; +} + +static int capemgr_remove_slot(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + int ret; + + mutex_lock(&info->slots_list_mutex); + ret = capemgr_remove_slot_no_lock(slot); + mutex_unlock(&info->slots_list_mutex); + + return ret; +} + +static int bone_slot_fill_override(struct bone_cape_slot *slot, + const char *part_number, const char *version) +{ + const struct ee_field *sig_field; + int i, len, has_part_number; + char *p; + + slot->probe_failed = 0; + slot->probed = 0; + + /* zero out signature */ + memset(slot->signature, 0, + sizeof(slot->signature)); + + /* first, fill in all with override defaults */ + for (i = 0; i < ARRAY_SIZE(cape_sig_fields); i++) { + + sig_field = &cape_sig_fields[i]; + + /* point to the entry */ + p = slot->signature + sig_field->start; + + if (sig_field->override) + memcpy(p, sig_field->override, + sig_field->size); + else + memset(p, 0, sig_field->size); + } + + /* if a part_number is supplied use it */ + len = part_number ? strlen(part_number) : 0; + if (len > 0) { + sig_field = &cape_sig_fields[CAPE_EE_FIELD_PART_NUMBER]; + + /* point to the entry */ + p = slot->signature + sig_field->start; + + /* copy and zero out any remainder */ + if (len > sig_field->size) + len = sig_field->size; + memcpy(p, part_number, len); + if (len < sig_field->size) + memset(p + len, 0, sig_field->size - len); + + has_part_number = 1; + } + + /* if a version is supplied use it */ + len = version ? strlen(version) : 0; + if (len > 0) { + sig_field = &cape_sig_fields[CAPE_EE_FIELD_VERSION]; + + /* point to the entry */ + p = slot->signature + sig_field->start; + + /* copy and zero out any remainder */ + if (len > sig_field->size) + len = sig_field->size; + memcpy(p, version, len); + if (len < sig_field->size) + memset(p + len, 0, sig_field->size - len); + } + + /* we must have a part number */ + if (!has_part_number) + return -EINVAL; + + slot->override = 1; + + return 0; +} + +static struct bone_cape_slot * +capemgr_add_slot(struct capemgr_info *info, const char *slot_name, + const char *part_number, const char *version, int prio) +{ + struct bone_cape_slot *slot; + struct device *dev = &info->pdev->dev; + int slotno; + int ret; + + slotno = atomic_inc_return(&info->next_slot_nr) - 1; + + slot = devm_kzalloc(dev, sizeof(*slot), GFP_KERNEL); + if (slot == NULL) + return ERR_PTR(-ENOMEM); + + slot->info = info; + slot->slotno = slotno; + slot->priority = prio; + slot->overlay_id = -1; + + if (slot_name) { + slot->nvmem_cell = nvmem_cell_get(dev, slot_name); + if (IS_ERR(slot->nvmem_cell)) { + ret = PTR_ERR(slot->nvmem_cell); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get slot eeprom cell\n"); + slot->nvmem_cell = NULL; + goto err_out; + } + } else { + dev_info(dev, "slot #%d: override\n", slotno); + + /* fill in everything with defaults first */ + ret = bone_slot_fill_override(slot, part_number, version); + if (ret != 0) { + dev_err(dev, "slot #%d: override failed\n", slotno); + goto err_out; + } + } + + ret = bone_slot_scan(slot); + if (ret != 0) { + + if (!slot->probe_failed) { + dev_err(dev, "slot #%d: scan failed\n", + slotno); + goto err_out; + } + + dev_err(dev, "slot #%d: No cape found\n", slotno); + /* but all is fine */ + } else { + + dev_info(dev, "slot #%d: '%s'\n", + slotno, slot->text_id); + + ret = bone_cape_slot_sysfs_register(slot); + if (ret != 0) { + dev_err(dev, "slot #%d: sysfs register failed\n", + slotno); + goto err_out; + } + + } + + /* add to the slot list */ + mutex_lock(&info->slots_list_mutex); + list_add_tail(&slot->node, &info->slot_list); + mutex_unlock(&info->slots_list_mutex); + + return slot; + +err_out: + if (slot->nvmem_cell) + nvmem_cell_put(slot->nvmem_cell); + devm_kfree(dev, slot); + return ERR_PTR(ret); +} + +/* return 1 if it makes sense to retry loading */ +static int retry_loading_condition(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + struct bone_cape_slot *slotn; + int ret; + + dev_dbg(dev, "loader: retry_loading slot-%d %s:%s (prio %d)\n", + slot->slotno, slot->part_number, slot->version, + slot->priority); + + mutex_lock(&info->slots_list_mutex); + ret = 0; + list_for_each_entry(slotn, &info->slot_list, node) { + /* if same slot or not loading skip */ + if (!slotn->loading || slotn->retry_loading) + continue; + /* at least one cape is still loading (without retrying) */ + ret = 1; + } + mutex_unlock(&info->slots_list_mutex); + return ret; +} + +/* return 1 if this slot is clear to try to load now */ +static int clear_to_load_condition(struct bone_cape_slot *slot) +{ + struct capemgr_info *info = slot->info; + int my_prio = slot->priority; + struct device *dev = &info->pdev->dev; + int ret; + + dev_dbg(dev, "loader: check slot-%d %s:%s (prio %d)\n", slot->slotno, + slot->part_number, slot->version, slot->priority); + + mutex_lock(&info->slots_list_mutex); + ret = 1; + list_for_each_entry(slot, &info->slot_list, node) { + /* if any slot is loading with lowest priority */ + if (!slot->loading) + continue; + if (slot->priority < my_prio) { + ret = 0; + break; + } + } + mutex_unlock(&info->slots_list_mutex); + return ret; +} + +static int capemgr_loader(void *data) +{ + struct bone_cape_slot *slot = data; + struct capemgr_info *info = slot->info; + struct device *dev = &info->pdev->dev; + int ret, done, other_loading, booting; + + done = 0; + + slot->retry_loading = 0; + + dev_dbg(dev, "loader: before slot-%d %s:%s (prio %d)\n", slot->slotno, + slot->part_number, slot->version, slot->priority); + + /* + * We have a basic priority based arbitration system + * Slots have priorities, so the lower priority ones + * should start loading first. So each time we end up + * here. + */ + ret = wait_event_interruptible(info->load_wq, + clear_to_load_condition(slot)); + if (ret < 0) { + dev_warn(dev, "loader, Signal pending\n"); + return ret; + } + + dev_dbg(dev, "loader: after slot-%d %s:%s (prio %d)\n", slot->slotno, + slot->part_number, slot->version, slot->priority); + + /* using the return value */ + ret = capemgr_load_slot(slot); + + /* wake up all just in case */ + wake_up_interruptible_all(&info->load_wq); + + if (ret == 0) + goto done; + + dev_dbg(dev, "loader: retrying slot-%d %s:%s (prio %d)\n", slot->slotno, + slot->part_number, slot->version, slot->priority); + + /* first attempt has failed; now try each time there's any change */ + slot->retry_loading = 1; + + for (;;) { + booting = (system_state == SYSTEM_BOOTING); + other_loading = retry_loading_condition(slot); + if (!booting && !other_loading) + break; + + /* simple wait for someone to kick us */ + if (other_loading) { + DEFINE_WAIT(__wait); + + prepare_to_wait(&info->load_wq, &__wait, + TASK_INTERRUPTIBLE); + finish_wait(&info->load_wq, &__wait); + } else { + /* always delay when booting */ + msleep(boot_scan_period); + } + + if (signal_pending(current)) { + dev_warn(dev, "loader, Signal pending\n"); + ret = -ERESTARTSYS; + goto done; + } + + /* using the return value */ + ret = capemgr_load_slot(slot); + if (ret == 0) + goto done; + + /* wake up all just in case */ + wake_up_interruptible_all(&info->load_wq); + } + +done: + slot->loading = 0; + slot->retry_loading = 0; + + if (ret == 0) { + dev_dbg(dev, "loader: done slot-%d %s:%s (prio %d)\n", + slot->slotno, slot->part_number, slot->version, + slot->priority); + } else { + dev_err(dev, "loader: failed to load slot-%d %s:%s (prio %d)\n", + slot->slotno, slot->part_number, slot->version, + slot->priority); + + /* if it's a override slot remove it */ + if (slot->override) + capemgr_remove_slot(slot); + } + + return ret; +} + +static int +capemgr_probe(struct platform_device *pdev) +{ + struct capemgr_info *info; + struct bone_baseboard *bbrd; + struct bone_cape_slot *slot; + struct device_node *pnode = pdev->dev.of_node; + struct device_node *baseboardmaps_node; + struct device_node *node; + const char *part_number; + const char *version; + const char *board_name; + const char *compatible_name; + char slot_name[16]; + u32 slots_nr; + int i, ret, len, prio; + long val; + char *wbuf, *s, *p, *e; + + /* we don't use platform_data at all; we require OF */ + if (pnode == NULL) + return -ENOTSUPP; + + info = devm_kzalloc(&pdev->dev, + sizeof(struct capemgr_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + platform_set_drvdata(pdev, info); + + atomic_set(&info->next_slot_nr, 0); + INIT_LIST_HEAD(&info->slot_list); + mutex_init(&info->slots_list_mutex); + + init_waitqueue_head(&info->load_wq); + + baseboardmaps_node = NULL; + + /* find the baseboard */ + bbrd = &info->baseboard; + + baseboardmaps_node = of_get_child_by_name(pnode, "baseboardmaps"); + if (baseboardmaps_node == NULL) { + dev_err(&pdev->dev, "Failed to get baseboardmaps node"); + ret = -ENODEV; + goto err_exit; + } + + bbrd->nvmem_cell = nvmem_cell_get(&pdev->dev, "baseboard"); + if (IS_ERR(bbrd->nvmem_cell)) { + ret = PTR_ERR(bbrd->nvmem_cell); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get baseboard eeprom cell\n"); + bbrd->nvmem_cell = NULL; + goto err_exit; + } + + ret = bone_baseboard_scan(bbrd); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to scan baseboard eeprom\n"); + goto err_exit; + } + + dev_info(&pdev->dev, "Baseboard: '%s'\n", bbrd->text_id); + + board_name = NULL; + compatible_name = NULL; + for_each_child_of_node(baseboardmaps_node, node) { + /* there must be board-name */ + if (of_property_read_string(node, "board-name", + &board_name) != 0 || + of_property_read_string(node, "compatible-name", + &compatible_name) != 0) + continue; + + if (strcmp(bbrd->board_name, board_name) == 0) + break; + } + of_node_put(baseboardmaps_node); + baseboardmaps_node = NULL; + + if (node == NULL) { + dev_err(&pdev->dev, "Failed to find compatible map for %s\n", + bbrd->board_name); + ret = -ENODEV; + goto err_exit; + } + bbrd->compatible_name = kstrdup(compatible_name, GFP_KERNEL); + if (bbrd->compatible_name == NULL) { + ret = -ENOMEM; + goto err_exit; + } + of_node_put(node); + + /* get slot number */ + ret = of_property_read_u32(pnode, "#slots", &slots_nr); + if (ret != 0) + slots_nr = 0; + + dev_info(&pdev->dev, "compatible-baseboard=%s - #slots=%d\n", + bbrd->compatible_name, slots_nr); + + for (i = 0; i < slots_nr; i++) { + snprintf(slot_name, sizeof(slot_name), "slot%d", i); + slot = capemgr_add_slot(info, slot_name, NULL, NULL, 0); + if (IS_ERR(slot)) { + dev_err(&pdev->dev, "Failed to add slot #%d\n", + atomic_read(&info->next_slot_nr)); + ret = PTR_ERR(slot); + goto err_exit; + } + } + + /* iterate over enable_partno (if there) */ + if (enable_partno && strlen(enable_partno) > 0) { + + /* allocate a temporary buffer */ + wbuf = devm_kzalloc(&pdev->dev, PAGE_SIZE, GFP_KERNEL); + if (wbuf == NULL) { + ret = -ENOMEM; + goto err_exit; + } + + /* add any enable_partno capes */ + s = enable_partno; + while (*s) { + /* form is PART[:REV[:PRIO]],PART.. */ + p = strchr(s, ','); + if (p == NULL) + e = s + strlen(s); + else + e = p; + + /* copy to temp buffer */ + len = e - s; + if (len >= PAGE_SIZE - 1) + len = PAGE_SIZE - 1; + memcpy(wbuf, s, len); + wbuf[len] = '\0'; + + /* move to the next */ + s = *e ? e + 1 : e; + + part_number = wbuf; + + /* default version is NULL & prio is 0 */ + version = NULL; + prio = 0; + + /* now split the rev & prio part */ + p = strchr(wbuf, ':'); + if (p != NULL) { + *p++ = '\0'; + if (*p != ':') + version = p; + p = strchr(p, ':'); + if (p != NULL) { + *p++ = '\0'; + ret = kstrtol(p, 10, &val); + if (ret == 0) + prio = val; + } + } + + dev_info(&pdev->dev, + "enabled_partno PARTNO '%s' VER '%s' PR '%d'\n", + part_number, + version ? version : "N/A", prio); + + /* only immediate slots are allowed here */ + slot = capemgr_add_slot(info, NULL, + part_number, version, prio); + + /* we continue even in case of an error */ + if (IS_ERR_OR_NULL(slot)) { + dev_warn(&pdev->dev, "Failed to add slot #%d\n", + atomic_read(&info->next_slot_nr) - 1); + } + } + + devm_kfree(&pdev->dev, wbuf); + } + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&pdev->dev); + if (IS_ERR_VALUE(ret)) { + dev_err(&pdev->dev, "Failed to pm_runtime_get_sync()\n"); + goto err_exit; + } + + pm_runtime_put(&pdev->dev); + + /* it is safe to create the attribute groups */ + ret = sysfs_create_groups(&pdev->dev.kobj, attr_groups); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to create sysfs attributes\n"); + goto err_exit; + } + /* automatically cleared by driver core now */ + pdev->dev.groups = attr_groups; + + /* now load each (take lock to be sure */ + mutex_lock(&info->slots_list_mutex); + + list_for_each_entry(slot, &info->slot_list, node) { + + /* if matches the disabled ones skip */ + if (bone_match_cape(disable_partno, slot->part_number, + slot->version)) { + dev_info(&pdev->dev, + "Skipping loading of disabled cape with part# %s\n", + slot->part_number); + slot->disabled = 1; + continue; + } + + if (!slot->probe_failed && !slot->loaded) + slot->loading = 1; + } + + /* now start the loader thread(s) (all at once) */ + list_for_each_entry(slot, &info->slot_list, node) { + + if (!slot->loading) + continue; + + slot->loader_thread = kthread_run(capemgr_loader, + slot, "capemgr-loader-%d", + slot->slotno); + if (IS_ERR(slot->loader_thread)) { + dev_warn(&pdev->dev, "slot #%d: Failed to start loader\n", + slot->slotno); + slot->loader_thread = NULL; + } + } + mutex_unlock(&info->slots_list_mutex); + + dev_info(&pdev->dev, "initialized OK.\n"); + + return 0; + +err_exit: + if (bbrd->nvmem_cell) + nvmem_cell_put(bbrd->nvmem_cell); + of_node_put(baseboardmaps_node); + platform_set_drvdata(pdev, NULL); + devm_kfree(&pdev->dev, info); + + return ret; +} + +static int capemgr_remove(struct platform_device *pdev) +{ + struct capemgr_info *info = platform_get_drvdata(pdev); + struct bone_baseboard *bbrd = &info->baseboard; + struct bone_cape_slot *slot, *slotn; + int ret; + + mutex_lock(&info->slots_list_mutex); + list_for_each_entry_safe(slot, slotn, &info->slot_list, node) + capemgr_remove_slot_no_lock(slot); + mutex_unlock(&info->slots_list_mutex); + + platform_set_drvdata(pdev, NULL); + + ret = pm_runtime_get_sync(&pdev->dev); + if (IS_ERR_VALUE(ret)) + return ret; + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + if (bbrd->nvmem_cell) + nvmem_cell_put(bbrd->nvmem_cell); + devm_kfree(&pdev->dev, info); + + return 0; +} + +static struct platform_driver capemgr_driver = { + .probe = capemgr_probe, + .remove = capemgr_remove, + .driver = { + .name = "bone_capemgr", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(capemgr_of_match), + }, +}; + +module_platform_driver(capemgr_driver); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("Beaglebone cape manager"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bone_capemgr"); diff --git b/drivers/misc/cape/Kconfig b/drivers/misc/cape/Kconfig new file mode 100644 index 0000000..a2ef85e --- /dev/null +++ b/drivers/misc/cape/Kconfig @@ -0,0 +1,5 @@ +# +# Capes +# + +source "drivers/misc/cape/beaglebone/Kconfig" diff --git b/drivers/misc/cape/Makefile b/drivers/misc/cape/Makefile new file mode 100644 index 0000000..7c4eb96 --- /dev/null +++ b/drivers/misc/cape/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for cape like devices +# + +obj-y += beaglebone/ diff --git b/drivers/misc/cape/beaglebone/Kconfig b/drivers/misc/cape/beaglebone/Kconfig new file mode 100644 index 0000000..eeb6782 --- /dev/null +++ b/drivers/misc/cape/beaglebone/Kconfig @@ -0,0 +1,10 @@ +# +# Beaglebone capes +# + +config BEAGLEBONE_PINMUX_HELPER + tristate "Beaglebone Pinmux Helper" + depends on ARCH_OMAP2PLUS && OF + default n + help + Say Y here to include support for the pinmux helper diff --git b/drivers/misc/cape/beaglebone/Makefile b/drivers/misc/cape/beaglebone/Makefile new file mode 100644 index 0000000..7f4617a --- /dev/null +++ b/drivers/misc/cape/beaglebone/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for beaglebone capes +# + +obj-$(CONFIG_BEAGLEBONE_PINMUX_HELPER) += bone-pinmux-helper.o diff --git b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c new file mode 100644 index 0000000..d81363a --- /dev/null +++ b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c @@ -0,0 +1,242 @@ +/* + * Pinmux helper driver + * + * Copyright (C) 2013 Pantelis Antoniou + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct of_device_id bone_pinmux_helper_of_match[] = { + { + .compatible = "bone-pinmux-helper", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, bone_pinmux_helper_of_match); + +struct pinmux_helper_data { + struct pinctrl *pinctrl; + char *selected_state_name; +}; + +static ssize_t pinmux_helper_show_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + const char *name; + + name = data->selected_state_name; + if (name == NULL || strlen(name) == 0) + name = "none"; + return sprintf(buf, "%s\n", name); +} + +static ssize_t pinmux_helper_store_state(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + struct pinctrl_state *state; + char *state_name; + char *s; + int err; + + /* duplicate (as a null terminated string) */ + state_name = kmalloc(count + 1, GFP_KERNEL); + if (state_name == NULL) + return -ENOMEM; + memcpy(state_name, buf, count); + state_name[count] = '\0'; + + /* and chop off newline */ + s = strchr(state_name, '\n'); + if (s != NULL) + *s = '\0'; + + /* try to select default state at first (if it exists) */ + state = pinctrl_lookup_state(data->pinctrl, state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) + dev_err(dev, "Failed to select state %s\n", + state_name); + } else { + dev_err(dev, "Failed to find state %s\n", state_name); + err = PTR_RET(state); + } + + if (err == 0) { + kfree(data->selected_state_name); + data->selected_state_name = state_name; + } + + return err ? err : count; +} + +static DEVICE_ATTR(state, S_IWUSR | S_IRUGO, + pinmux_helper_show_state, pinmux_helper_store_state); + +static struct attribute *pinmux_helper_attributes[] = { + &dev_attr_state.attr, + NULL +}; + +static const struct attribute_group pinmux_helper_attr_group = { + .attrs = pinmux_helper_attributes, +}; + +static int bone_pinmux_helper_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pinmux_helper_data *data; + struct pinctrl_state *state; + char *state_name; + const char *mode_name; + int mode_len; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) { + dev_err(dev, "Failed to allocate data\n"); + err = -ENOMEM; + goto err_no_mem; + } + + state_name = kmalloc(strlen(PINCTRL_STATE_DEFAULT) + 1, + GFP_KERNEL); + if (state_name == NULL) { + dev_err(dev, "Failed to allocate state name\n"); + err = -ENOMEM; + goto err_no_state_mem; + } + data->selected_state_name = state_name; + strcpy(data->selected_state_name, PINCTRL_STATE_DEFAULT); + + platform_set_drvdata(pdev, data); + + data->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(data->pinctrl)) { + dev_err(dev, "Failed to get pinctrl\n"); + err = PTR_RET(data->pinctrl); + goto err_no_pinctrl; + } + + /* See if an initial mode is specified in the device tree */ + mode_name = of_get_property(dev->of_node, "mode", &mode_len); + + err = -1; + if (mode_name != NULL ) { + state_name = kmalloc(mode_len + 1, GFP_KERNEL); + if (state_name == NULL) { + dev_err(dev, "Failed to allocate state name\n"); + err = -ENOMEM; + goto err_no_mode_mem; + } + strncpy(state_name, mode_name, mode_len); + + /* try to select requested mode */ + state = pinctrl_lookup_state(data->pinctrl, state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) { + dev_warn(dev, "Unable to select requested mode %s\n", state_name); + kfree(state_name); + } else { + kfree(data->selected_state_name); + data->selected_state_name = state_name; + dev_notice(dev, "Set initial pinmux mode to %s\n", state_name); + } + } + } + + /* try to select default state if mode_name failed */ + if ( err != 0) { + state = pinctrl_lookup_state(data->pinctrl, + data->selected_state_name); + if (!IS_ERR(state)) { + err = pinctrl_select_state(data->pinctrl, state); + if (err != 0) { + dev_err(dev, "Failed to select default state\n"); + goto err_no_state; + } + } else { + data->selected_state_name = '\0'; + } + } + + /* Register sysfs hooks */ + err = sysfs_create_group(&dev->kobj, &pinmux_helper_attr_group); + if (err) { + dev_err(dev, "Failed to create sysfs group\n"); + goto err_no_sysfs; + } + + return 0; + +err_no_sysfs: +err_no_state: +err_no_mode_mem: + devm_pinctrl_put(data->pinctrl); +err_no_pinctrl: + devm_kfree(dev, data->selected_state_name); +err_no_state_mem: + devm_kfree(dev, data); +err_no_mem: + return err; +} + +static int bone_pinmux_helper_remove(struct platform_device *pdev) +{ + struct pinmux_helper_data *data = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + sysfs_remove_group(&dev->kobj, &pinmux_helper_attr_group); + kfree(data->selected_state_name); + devm_pinctrl_put(data->pinctrl); + devm_kfree(dev, data); + + return 0; +} + +struct platform_driver bone_pinmux_helper_driver = { + .probe = bone_pinmux_helper_probe, + .remove = bone_pinmux_helper_remove, + .driver = { + .name = "bone-pinmux-helper", + .owner = THIS_MODULE, + .of_match_table = bone_pinmux_helper_of_match, + }, +}; + +module_platform_driver(bone_pinmux_helper_driver); + +MODULE_AUTHOR("Pantelis Antoniou"); +MODULE_DESCRIPTION("Beaglebone pinmux helper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bone-pinmux-helper"); diff --git b/drivers/misc/cape_bone_argus/Kconfig b/drivers/misc/cape_bone_argus/Kconfig new file mode 100644 index 0000000..1b39661 --- /dev/null +++ b/drivers/misc/cape_bone_argus/Kconfig @@ -0,0 +1,7 @@ +comment "Argus cape driver for beaglebone black" + +config CAPE_BONE_ARGUS + tristate "Argus Cape Driver" + default M + help + Argus Cape Driver diff --git b/drivers/misc/cape_bone_argus/Makefile b/drivers/misc/cape_bone_argus/Makefile new file mode 100644 index 0000000..5482562 --- /dev/null +++ b/drivers/misc/cape_bone_argus/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for Argus cape +# + +obj-$(CONFIG_CAPE_BONE_ARGUS) += cape_bone_argus.o diff --git b/drivers/misc/cape_bone_argus/cape_bone_argus.c b/drivers/misc/cape_bone_argus/cape_bone_argus.c new file mode 100644 index 0000000..c434218 --- /dev/null +++ b/drivers/misc/cape_bone_argus/cape_bone_argus.c @@ -0,0 +1,415 @@ +/* -*- linux-c -*- */ + +/* Linux Kernel Module for Breakaway Systems UPS control. + * + * PUBLIC DOMAIN + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Module to sync file systems leaving them mounted read-only, + * then signal the UPS that it is safe to remove + * power, and finally halt the processor. + * Also to allow kicking the watchdog from user mode. + */ +#undef DEBUG_ARGUS + +#define N_GPIOS 9 /* Total number of GPIOS used */ + +#define REQ_GPIO_IDX 0 /* Indices got GPIOS */ +#define ACK_GPIO_IDX 1 +#define WDG_GPIO_IDX 2 +#define LED1_GREEN_IDX 3 +#define LED1_RED_IDX 4 +#define LED2_GREEN_IDX 5 +#define LED2_RED_IDX 6 +#define GEN_OUT1_IDX 7 +#define GEN_OUT2_IDX 8 + +static struct argus_ups_info { /* As there is only one UPS device we can make this static */ + struct fasync_struct *async_queue; /* asynchronous readers */ + struct platform_device *pdev; + struct pwm_device *pwm_dev; + struct gpio gpios[N_GPIOS]; +} info = {NULL, NULL, NULL, /* Some fields filled in by device tree, probe, etc. */ + { + {-1, GPIOF_IN, "Powerdown request"}, + {-1, GPIOF_OUT_INIT_LOW, "Powerdown acknowledge" }, + {-1, GPIOF_OUT_INIT_LOW, "Watchdog"}, + {-1, GPIOF_OUT_INIT_LOW, "LED 1 Green"}, + {-1, GPIOF_OUT_INIT_LOW, "LED 1 Red"}, + {-1, GPIOF_OUT_INIT_LOW, "LED 2 Green"}, + {-1, GPIOF_OUT_INIT_LOW, "LED 2 Red"}, + {-1, GPIOF_OUT_INIT_LOW, "General Output #1"}, + {-1, GPIOF_OUT_INIT_LOW, "General Output #2"} + }, +}; + + +static const struct of_device_id argus_ups_of_ids[] = { + { .compatible = "argus-ups" }, + { } +}; + +static int argus_ups_major; /* Major device number */ + +static struct class *argus_ups_class; /* /sys/class */ + +dev_t argus_ups_dev; /* Device number */ + +static struct cdev *argus_ups_cdev; /* Character device details */ + +static void argus_ups_function(struct work_struct *ignored); /* Work function */ + +static DECLARE_DELAYED_WORK(argus_ups_work, argus_ups_function); /* Kernel workqueue glue */ + +static struct workqueue_struct *argus_ups_workqueue; /* Kernel workqueue */ + +static int debug = 0; +module_param(debug, int, S_IRUGO); +MODULE_PARM_DESC(debug, "Debug flag"); + +static int shutdown = 1; +module_param(shutdown, int, S_IRUGO); +MODULE_PARM_DESC(shutdown, "Shutdown flag"); + +#ifdef DEBUG_ARGUS +static char* fs_type_names[] = {"vfat", "ext4"}; /* File system names that may need syncing. */ +#endif + +/* Just kick watchdog */ + +static ssize_t argus_ups_write(struct file *filp, const char __user *buf, size_t count, + loff_t *f_pos) +{ + int i; + if (debug >= 3) { + printk("Writing to watchdog - count:%d\n", count); + } + for (i = 0; i < count; i++) { + gpio_set_value(info.gpios[WDG_GPIO_IDX].gpio, 1); /* Set it */ + msleep(10); /* Wait */ + gpio_set_value(info.gpios[WDG_GPIO_IDX].gpio, 0); /* End clearing it */ + msleep(10); + } + return count; /* Always returns what we sent, regardsless */ +} + +static long argus_ups_ioctl(struct file *file, + unsigned int ioctl, + unsigned long param) +{ + if (debug >= 4) { + printk(KERN_ERR "ioctl: %d, param: %ld\n", ioctl, param); + } + switch(ioctl) { + case 10001: { + debug = param; + printk("Debug set to %d\n", debug); + break; + } + case 10002: { + unsigned char value = param & 0x0F; + unsigned char mask = (param >> 4) & 0x0F; + int i; /* Loop iterator */ + if (mask == 0) { + printk(KERN_ERR "Pointless mask of zero!\n"); + } + for (i = 0; i < 4; i++) { /* For all four LEDS */ + if (mask & (1 << i)) { /* Only masked values */ + if (value & (1 << i)) { /* On - so gpio is hi */ + if (debug >= 4) { + printk("Setting %d hi, ", + info.gpios[LED1_GREEN_IDX + i].gpio); + } + gpio_set_value(info.gpios[LED1_GREEN_IDX + i].gpio, 1); + } + else { /* Off - so gpio is lo */ + if (debug >= 4) { + printk("Setting %d lo, ", + info.gpios[LED1_GREEN_IDX + i].gpio); + } + gpio_set_value(info.gpios[LED1_GREEN_IDX + i].gpio, 0); + } + } + } + if (debug >= 4) { + printk("\n"); + } + break; + } + case 10003: { + gpio_set_value(info.gpios[GEN_OUT1_IDX].gpio, param & 1); + break; + } + case 10004: { + gpio_set_value(info.gpios[GEN_OUT2_IDX].gpio, param & 1); + break; + } + default: + { + printk(KERN_ERR "Invalid ioctl %d\n", ioctl); + return -1; + } + } + return 0; +} + +static int argus_ups_fasync(int fd, struct file *filp, int mode) +{ + printk(KERN_ERR "In argus_ups_fasync() fd:%d, filp:%p, mode:%d\n", fd, filp, mode); + return fasync_helper(fd, filp, mode, &info.async_queue); +} + +static struct file_operations argus_ups_fops = { /* Only file operation is to kick watchdog via a write */ + .owner = THIS_MODULE, + .llseek = NULL, + .read = NULL, + .unlocked_ioctl = argus_ups_ioctl, + .write = argus_ups_write, + .open = NULL, + .release = NULL, + .fasync = argus_ups_fasync, +}; + +#ifdef DEBUG_ARGUS +static void remount_sb(struct super_block *sb) +{ + int flags = MS_RDONLY; + int result = sb->s_op->remount_fs(sb, &flags, ""); + if (debug) { + printk("Processing superblock %p\n", sb); + printk("Remount operation returned %d\n", result); + } +} +#endif + +static void argus_ups_function(struct work_struct *ignored) +{ + static int testdata = 0; /* Data for test */ + int i; /* Iterator */ + testdata++; + if (!gpio_get_value(info.gpios[REQ_GPIO_IDX].gpio)) { + queue_delayed_work(argus_ups_workqueue, &argus_ups_work, HZ/100); /* Re-queue in 10mS*/ + return; + } + printk(KERN_ERR "Request received\n"); + if (debug) { + printk("Shutdown request received from UPS\n"); + } + if (!shutdown) { + printk("Shutdown request ignored\n"); + return; + } + + if (debug) { + printk("Sending async kill SIGIO to %p\n", info.async_queue); + } + if (info.async_queue) { /* Try and tell usermode to halt system */ + kill_fasync(&info.async_queue, SIGIO, POLL_IN); + } + gpio_set_value(info.gpios[LED1_GREEN_IDX].gpio, 0); /* Turn off green LED1 */ + for (i = 0; i < 300; i++) { /* Toggle acknowledge at 10 Hz for 15 seconds */ + if (debug >= 2) { + printk("Waiting for first shutdown request:%d\n", i); + } + gpio_set_value(info.gpios[ACK_GPIO_IDX].gpio, i & 1); /* Toggle acknowledge */ + gpio_set_value(info.gpios[LED1_RED_IDX].gpio, i & 1); /* and LED1 red */ + msleep(50); /* Wait in 50ms increments */ + } + + { + char *argv[] = { "/sbin/halt", NULL }; + static char *envp[] = { + "HOME=/", + "TERM=linux", + "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin", NULL }; + + call_usermodehelper( argv[0], argv, envp, UMH_WAIT_PROC ); + } + for (i = 0; i < 300; i++) { /* Toggle acknowledge at 10 Hz for 15 more seconds */ + if (debug >= 2) { + printk("Waiting for second shutdown request:%d\n", i); + } + gpio_set_value(info.gpios[ACK_GPIO_IDX].gpio, i & 1); /* Toggle acknowledge */ + gpio_set_value(info.gpios[LED1_RED_IDX].gpio, i & 1); /* and LED1 red */ + msleep(50); /* Wait in 50ms increments */ + } + printk(KERN_ERR "Usermode failed to halt system\n"); + kernel_halt(); /* Last resort - may give some oopss */ +} + + +static int argus_ups_probe(struct platform_device *pdev) /* Entry point */ +{ + struct pinctrl *pinctrl; + struct device_node *pnode = pdev->dev.of_node; + int i; + int ret; + printk("Init UPS module - debug=%d, shutdown=%d\n", + debug, shutdown); + platform_set_drvdata(pdev, &info); + info.pdev = pdev; + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) { + dev_warn(&pdev->dev, + "pins are not configured from the driver\n"); + return -1; + } + ret = of_property_read_u32(pnode, "debug", &debug); + if (ret != 0) { + dev_err(&pdev->dev, "Unable to read debug parameter\n"); + } + else { + printk("Debug parameter read from DT:%d\n", debug); + } + + ret = of_property_read_u32(pnode, "shutdown", &shutdown); + if (ret != 0) { + dev_err(&pdev->dev, "Unable to read shutdown parameter\n"); + } + else { + printk("Shutdown parameter read from DT:%d\n", shutdown); + } + + ret = of_gpio_count(pnode); + + if (ret != N_GPIOS) { + printk(KERN_ERR "Wrong number of gpios"); + return -1; + } + + for (i = 0; i < of_gpio_count(pnode); i++) { + ret = of_get_gpio_flags(pnode, i, NULL); + if (debug) { + printk("GPIO#%d:%d\n", i, ret); + } + if (IS_ERR_VALUE(ret)) { + dev_err(&pdev->dev, "unable to get GPIO %d\n", i); + goto err_no_gpio; + } + info.gpios[i].gpio = ret; + } + + + ret = alloc_chrdev_region(&argus_ups_dev, 0, 2, "argus_ups"); + argus_ups_major = MAJOR(argus_ups_dev); + if (ret) { + printk(KERN_ERR "Error %d adding argus_ups\n", ret); + return -1; + } + if (debug) { + printk("argus_ups major: %d\n", argus_ups_major); + } + argus_ups_cdev = cdev_alloc(); /* Make this a character device */ + argus_ups_cdev->ops = &argus_ups_fops; /* File operations */ + argus_ups_cdev->owner = THIS_MODULE; /* Top level device */ + ret = cdev_add(argus_ups_cdev, argus_ups_dev, 1); /* Add it to the kernel */ + if (ret) { + printk(KERN_ERR "cdev_add returned %d\n", ret); + unregister_chrdev_region(0, 1); + return -1; + } + ret = gpio_request_array(info.gpios, N_GPIOS); + if (ret) { + printk(KERN_ERR "Error %d requesting GPIOs\n", ret); + unregister_chrdev_region(0, 1); + return -1; + } + + argus_ups_class = class_create(THIS_MODULE, "argus_ups"); /* /sys/class entry for udev */ + if (IS_ERR(argus_ups_class)) { + printk(KERN_ERR "Error creating argus_ups_class\n"); + unregister_chrdev_region(0, 1); + return -1; + } + device_create(argus_ups_class, NULL, MKDEV(argus_ups_major, 0), NULL, "argus_ups"); + argus_ups_workqueue = create_singlethread_workqueue("argus_ups"); + INIT_DELAYED_WORK(&argus_ups_work, argus_ups_function); + queue_delayed_work(argus_ups_workqueue, &argus_ups_work, 0); /* Start work immediately */ + + return 0; +err_no_gpio: + return ret; + +} + + +static void argus_ups_cleanup(void) +{ + printk("Module cleanup called\n"); + while (cancel_delayed_work(&argus_ups_work) == 0) { + flush_workqueue(argus_ups_workqueue); /* Make sure all work is completed */ + } + destroy_workqueue(argus_ups_workqueue); + gpio_free_array(info.gpios, N_GPIOS); + device_destroy(argus_ups_class, argus_ups_dev); + class_destroy(argus_ups_class); + unregister_chrdev_region(argus_ups_dev, 1); + cdev_del(argus_ups_cdev); +} + + + +static int argus_ups_remove(struct platform_device *pdev) +{ + printk("In argus_ups_remove()\n"); + argus_ups_cleanup(); + printk("After cleanup\n"); + return 0; +} + +#define ARGUS_UPS_PM_OPS NULL + +struct platform_driver argus_ups_driver = { + .probe = argus_ups_probe, + .remove = argus_ups_remove, + .driver = { + .name = "argus-ups", + .owner = THIS_MODULE, + .pm = ARGUS_UPS_PM_OPS, + .of_match_table = argus_ups_of_ids, + }, +}; + + +static int __init argus_ups_init(void) +{ + return platform_driver_probe(&argus_ups_driver, + argus_ups_probe); +} + +static void __exit argus_ups_exit(void) +{ + platform_driver_unregister(&argus_ups_driver); + printk("After driver unregister\n"); +} + +module_init(argus_ups_init); +module_exit(argus_ups_exit); + +/* + * Get rid of taint message. + */ +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Lambert"); /* Who wrote this module? */ +MODULE_DESCRIPTION("Argus UPS control"); /* What does this module do */ +MODULE_ALIAS("platform:argus-ups"); +MODULE_DEVICE_TABLE(of, argus_ups_of_ids); diff --git b/drivers/misc/devovmgr.c b/drivers/misc/devovmgr.c new file mode 100644 index 0000000..18ea187 --- /dev/null +++ b/drivers/misc/devovmgr.c @@ -0,0 +1,1300 @@ +/* + * Device overlay manager + * + * Copyright (C) 2015 Konsulko Group + * Pantelis Antoniou + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum dovmgr_type { + ITEM_PCI, + ITEM_USB +}; + +struct dovmgr_item; + +struct dovmgr_dev_item { + struct dovmgr_item *item; + struct list_head node; + struct device *dev; + const struct firmware *fw; + struct device_node *overlay; + int overlay_id; + struct work_struct work; +}; + +struct dovmgr_item { + struct config_item item; + char *path; + bool enable; + char *overlay_name; + struct mutex dev_item_mutex; + struct list_head dev_item_list; + enum dovmgr_type type; + union { + struct pci_device_id pci; + struct usb_device_id usb; + }; +}; + +struct config_group dovmgr_pci_group; +struct config_group dovmgr_usb_group; + +static inline struct dovmgr_item *to_dovmgr_item(struct config_item *cfsitem) +{ + if (!cfsitem) + return NULL; + + return container_of(cfsitem, struct dovmgr_item, item); +} + +static int dovmgr_notifier_action(struct config_group *group, + unsigned long action, struct device *dev, + int (*do_match)(struct dovmgr_item *item, struct device *dev), + int (*do_action)(struct dovmgr_item *item, unsigned long action, + struct device *dev)) +{ + struct config_item *cfsitem; + struct dovmgr_item *item; + int ret; + + /* only handle device notifiers */ + if (action != BUS_NOTIFY_ADD_DEVICE && + action != BUS_NOTIFY_DEL_DEVICE && + action != BUS_NOTIFY_REMOVED_DEVICE) + return 0; + + ret = 0; + mutex_lock(&group->cg_subsys->su_mutex); + list_for_each_entry(cfsitem, &group->cg_children, ci_entry) { + item = to_dovmgr_item(cfsitem); + if (!item->enable || !(*do_match)(item, dev)) + continue; + + ret = (*do_action)(item, action, dev); + if (ret != 0) + break; + } + mutex_unlock(&group->cg_subsys->su_mutex); + return ret; +} + +#if IS_ENABLED(CONFIG_PCI) +/* copy of drivers/pci/pci.h */ +static inline const struct pci_device_id * +pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev) +{ + if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) && + (id->device == PCI_ANY_ID || id->device == dev->device) && + (id->subvendor == PCI_ANY_ID || + id->subvendor == dev->subsystem_vendor) && + (id->subdevice == PCI_ANY_ID || + id->subdevice == dev->subsystem_device) && + !((id->class ^ dev->class) & id->class_mask)) + return id; + return NULL; +} + +static int dovmgr_pci_item_match(struct dovmgr_item *item, struct device *dev) +{ + struct pci_dev *pdev; + + BUG_ON(item->type != ITEM_PCI); + pdev = to_pci_dev(dev); + + return pci_match_one_device(&item->pci, pdev) != NULL; +} +#endif + +#if IS_ENABLED(CONFIG_USB) +/* in drivers/usb/core/driver.c */ +extern int usb_match_device(struct usb_device *dev, + const struct usb_device_id *id); + +static int dovmgr_usb_item_match(struct dovmgr_item *item, struct device *dev) +{ + struct usb_device *udev; + + BUG_ON(item->type != ITEM_USB); + udev = to_usb_device(dev); + + return usb_match_device(udev, &item->usb); +} +#endif + +static struct dovmgr_dev_item *dovmgr_lookup_dev_item(struct dovmgr_item *item, + struct device *dev) +{ + struct dovmgr_dev_item *ditem; + + list_for_each_entry(ditem, &item->dev_item_list, node) + if (ditem->dev == dev) + return ditem; + return NULL; +} + +static void dovmgr_item_work_func(struct work_struct *work) +{ + struct dovmgr_dev_item *ditem = container_of(work, + struct dovmgr_dev_item, work); + struct dovmgr_item *item = ditem->item; + struct device *dev; + struct device_node *np; + int err; + + mutex_lock(&item->dev_item_mutex); + + dev = ditem->dev; + np = dev->of_node; + if (!dev || !np || !item->overlay_name || ditem->overlay_id >= 0) + goto out_unlock; + + pr_info("%s: %s %s\n", __func__, + kobject_name(&dev->kobj), of_node_full_name(np)); + + err = request_firmware_direct(&ditem->fw, item->overlay_name, dev); + if (err != 0) { + pr_err("%s: %s failed to load firmware '%s'\n", __func__, + kobject_name(&dev->kobj), item->overlay_name); + goto out_unlock; + } + + of_fdt_unflatten_tree((void *)ditem->fw->data, &ditem->overlay); + if (ditem->overlay == NULL) { + pr_err("%s: %s failed to load firmware '%s'\n", __func__, + kobject_name(&dev->kobj), item->overlay_name); + goto out_release_fw; + } + + /* mark it as detached */ + of_node_set_flag(ditem->overlay, OF_DETACHED); + + /* perform resolution */ + err = of_resolve_phandles(ditem->overlay); + if (err != 0) { + pr_err("%s: %s failed to resolve tree\n", __func__, + kobject_name(&dev->kobj)); + goto out_release_overlay; + } + + err = of_overlay_create_target_root(ditem->overlay, np); + if (err < 0) { + pr_err("%s: %s failed to create overlay\n", __func__, + kobject_name(&dev->kobj)); + goto out_release_overlay; + } + ditem->overlay_id = err; + +out_unlock: + mutex_unlock(&item->dev_item_mutex); + return; + +out_release_overlay: + /* TODO: free the overlay, we can't right now cause + * the unflatten method does not track it */ + ditem->overlay = NULL; +out_release_fw: + release_firmware(ditem->fw); + ditem->fw = NULL; + goto out_unlock; +} + +/* dev item list mutex lock must be held */ +static int dovmgr_add_dev_item(struct dovmgr_item *item, struct device *dev) +{ + struct dovmgr_dev_item *ditem; + + /* first make sure there's no duplicate */ + if (dovmgr_lookup_dev_item(item, dev)) + return -EEXIST; + + /* add the device item */ + ditem = kzalloc(sizeof(*ditem), GFP_KERNEL); + if (!ditem) + return -ENOMEM; + ditem->overlay_id = -1; + ditem->dev = get_device(dev); + INIT_WORK(&ditem->work, dovmgr_item_work_func); + ditem->item = item; + + list_add_tail(&ditem->node, &item->dev_item_list); + + pr_info("%s: added device %s from item's %s list\n", __func__, + kobject_name(&dev->kobj), + config_item_name(&item->item)); + + /* now schedule the overlay application */ + if (item->overlay_name) + schedule_work(&ditem->work); + + return 0; +} + +static int dovmgr_remove_dev_item(struct dovmgr_item *item, struct device *dev) +{ + struct dovmgr_dev_item *ditem; + + /* find it */ + ditem = dovmgr_lookup_dev_item(item, dev); + if (!ditem) + return -ENODEV; + + if (work_pending(&ditem->work)) + cancel_work_sync(&ditem->work); + + if (ditem->overlay_id >= 0) { + of_overlay_destroy(ditem->overlay_id); + ditem->overlay_id = -1; + + } + + if (ditem->overlay) { + /* TODO: free the overlay, we can't right now cause + * the unflatten method does not track it */ + ditem->overlay = NULL; + } + + if (ditem->fw) { + /* TODO release_firmware(ditem->fw); */ + release_firmware(ditem->fw); + ditem->fw = NULL; + } + + put_device(ditem->dev); + list_del(&ditem->node); + + kfree(ditem); + + pr_info("%s: removed device %s from item's %s list\n", __func__, + kobject_name(&dev->kobj), + config_item_name(&item->item)); + + return 0; +} + +static int dovmgr_item_notify(struct dovmgr_item *item, + unsigned long action, struct device *dev) +{ + int ret; + + ret = 0; + mutex_lock(&item->dev_item_mutex); + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + pr_info("%s: BUS_NOTIFY_ADD_DEVICE for %s\n", __func__, + kobject_name(&dev->kobj)); + + ret = dovmgr_add_dev_item(item, dev); + if (ret != 0) + goto out_unlock; + + break; + + case BUS_NOTIFY_DEL_DEVICE: + pr_info("%s: BUS_NOTIFY_DEL_DEVICE for %s\n", __func__, + kobject_name(&dev->kobj)); + break; + + case BUS_NOTIFY_REMOVED_DEVICE: + pr_info("%s: BUS_NOTIFY_REMOVE_DEVICE for %s\n", __func__, + kobject_name(&dev->kobj)); + + ret = dovmgr_remove_dev_item(item, dev); + if (ret != 0) + goto out_unlock; + + break; + } + +out_unlock: + mutex_unlock(&item->dev_item_mutex); + + return ret; +} + +#if IS_ENABLED(CONFIG_PCI) +static int dovmgr_pci_add_iterator(struct device *dev, void *data) +{ + struct dovmgr_item *item = data; + + /* do add match */ + if (!item->enable || !dovmgr_pci_item_match(item, dev)) + return 0; + + pr_info("%s: dev=%s\n", __func__, kobject_name(&dev->kobj)); + + return dovmgr_item_notify(item, BUS_NOTIFY_ADD_DEVICE, dev); +} + +static int dovmgr_pci_removed_iterator(struct device *dev, void *data) +{ + struct dovmgr_item *item = data; + + /* do add match */ + if (item->enable || !dovmgr_pci_item_match(item, dev)) + return 0; + + pr_info("%s: dev=%s\n", __func__, kobject_name(&dev->kobj)); + + return dovmgr_item_notify(item, BUS_NOTIFY_REMOVED_DEVICE, dev); +} +#endif + +#if IS_ENABLED(CONFIG_USB) +static int dovmgr_usb_add_iterator(struct device *dev, void *data) +{ + struct dovmgr_item *item = data; + + /* do add match */ + if (item->enable || !dovmgr_usb_item_match(item, dev)) + return 0; + + pr_info("%s: dev=%s\n", __func__, kobject_name(&dev->kobj)); + + return dovmgr_item_notify(item, BUS_NOTIFY_ADD_DEVICE, dev); +} + +static int dovmgr_usb_removed_iterator(struct device *dev, void *data) +{ + struct dovmgr_item *item = data; + + /* do add match */ + if (!item->enable || !dovmgr_usb_item_match(item, dev)) + return 0; + + pr_info("%s: dev=%s\n", __func__, kobject_name(&dev->kobj)); + + return dovmgr_item_notify(item, BUS_NOTIFY_REMOVED_DEVICE, dev); +} +#endif + +static int dovmgr_item_set_enable(struct dovmgr_item *item, bool new_enable) +{ + int ret; + + if (new_enable == item->enable) + return 0; + + item->enable = new_enable; + switch (item->type) { +#if IS_ENABLED(CONFIG_PCI) + case ITEM_PCI: + ret = bus_for_each_dev(&pci_bus_type, NULL, item, + new_enable ? dovmgr_pci_add_iterator : + dovmgr_pci_removed_iterator); + if (ret != 0) + return ret; + break; +#endif +#if IS_ENABLED(CONFIG_USB) + case ITEM_USB: + ret = bus_for_each_dev(&usb_bus_type, NULL, item, + new_enable ? dovmgr_usb_add_iterator : + dovmgr_usb_removed_iterator); + if (ret != 0) + return ret; + break; +#endif + default: + break; + } + return 0; +} + + +static ssize_t dovmgr_item_str_show(struct dovmgr_item *item, + char *page, char **strp) +{ + return snprintf(page, PAGE_SIZE, "%s\n", + *strp ? *strp : ""); +} + +static ssize_t dovmgr_item_str_store(struct dovmgr_item *item, + const char *page, size_t count, char **strp) +{ + const char *s; + int len; + + /* copy to path buffer (and make sure it's always zero terminated */ + len = strnlen(page, PAGE_SIZE); + if (len >= PAGE_SIZE) + return -EINVAL; + s = page + len; + while (len > 0 && *--s == '\n') + len--; + if (len == 0) + return -EINVAL; + + if (*strp) + kfree(*strp); + *strp = kmalloc(len + 1, GFP_KERNEL); + if (!*strp) + return -ENOMEM; + memcpy(*strp, page, len); + (*strp)[len + 1] = '\0'; + + return count; +} + +static ssize_t dovmgr_item_path_show(struct config_item *citem, char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return dovmgr_item_str_show(item, page, &item->path); +} + +static ssize_t dovmgr_item_path_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return dovmgr_item_str_store(item, page, count, &item->path); +} + +static ssize_t dovmgr_item_enable_show(struct config_item *citem, char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "%u\n", !!item->enable); +} + +static ssize_t dovmgr_item_enable_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + + ret = dovmgr_item_set_enable(item, !!val); + if (ret != 0) + return ret; + + return count; +} + +static ssize_t dovmgr_item_overlay_show(struct config_item *citem, char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + ssize_t ret; + + mutex_lock(&item->dev_item_mutex); + ret = snprintf(page, PAGE_SIZE, "%s\n", item->overlay_name ? + item->overlay_name : ""); + mutex_unlock(&item->dev_item_mutex); + return ret; +}; + + +static ssize_t dovmgr_item_overlay_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + ssize_t ret; + + mutex_lock(&item->dev_item_mutex); + kfree(item->overlay_name); + item->overlay_name = kstrndup(page, PAGE_SIZE, GFP_KERNEL); + if (!item->overlay_name) + ret = -ENOMEM; + else + ret = count; + mutex_unlock(&item->dev_item_mutex); + return ret; +} + +static ssize_t dovmgr_item_status_show(struct config_item *citem, char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + struct dovmgr_dev_item *ditem; + char *p, *e; + int len; + + p = page; + e = page + PAGE_SIZE; + + mutex_lock(&item->dev_item_mutex); + list_for_each_entry(ditem, &item->dev_item_list, node) { + len = snprintf(p, e - p, "%s:%s:%d\n", + kobject_name(&ditem->dev->kobj), + of_node_full_name(ditem->dev->of_node), + ditem->overlay_id); + p += len; + if (p >= e - 1) + break; + } + mutex_unlock(&item->dev_item_mutex); + + return p - page; +} + +CONFIGFS_ATTR(dovmgr_item_, path); +CONFIGFS_ATTR_RO(dovmgr_item_, status); +CONFIGFS_ATTR(dovmgr_item_, enable); +CONFIGFS_ATTR(dovmgr_item_, overlay); + +#if IS_ENABLED(CONFIG_PCI) +static ssize_t dovmgr_item_pci_device_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.device); +} + +static ssize_t dovmgr_item_pci_device_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.device = val; + return count; +} + +static ssize_t dovmgr_item_pci_vendor_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.vendor); +} + +static ssize_t dovmgr_item_pci_vendor_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.vendor = val; + return count; +} + +static ssize_t dovmgr_item_pci_subdevice_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%08x\n", item->pci.subdevice); +} + +static ssize_t dovmgr_item_pci_subdevice_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.subdevice = val; + return count; +} + +static ssize_t dovmgr_item_pci_subvendor_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%08x\n", item->pci.subvendor); +} + +static ssize_t dovmgr_item_pci_subvendor_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.subvendor = val; + return count; +} + +static ssize_t dovmgr_item_pci_class_show(struct config_item *citem, char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.class); +} + +static ssize_t dovmgr_item_pci_class_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.class = val; + return count; +} + +static ssize_t dovmgr_item_pci_class_mask_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.class_mask); +} + +static ssize_t dovmgr_item_pci_class_mask_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->pci.class_mask = val; + return count; +} + +CONFIGFS_ATTR(dovmgr_item_pci_, device); +CONFIGFS_ATTR(dovmgr_item_pci_, vendor); +CONFIGFS_ATTR(dovmgr_item_pci_, subdevice); +CONFIGFS_ATTR(dovmgr_item_pci_, subvendor); +CONFIGFS_ATTR(dovmgr_item_pci_, class); +CONFIGFS_ATTR(dovmgr_item_pci_, class_mask); +#endif + +#if IS_ENABLED(CONFIG_USB) +static ssize_t dovmgr_item_usb_idProduct_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", + item->usb.idProduct); +} + +static ssize_t dovmgr_item_usb_idProduct_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->usb.idProduct = val; + return count; +} + +static ssize_t dovmgr_item_usb_idVendor_show(struct config_item *citem, + char *page) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + return snprintf(page, PAGE_SIZE, "0x%04x\n", + item->usb.idVendor); +} + +static ssize_t dovmgr_item_usb_idVendor_store(struct config_item *citem, + const char *page, size_t count) +{ + struct dovmgr_item *item = to_dovmgr_item(citem); + int ret; + unsigned int val; + + /* cannot modify when item is enabled */ + if (item->enable) + return -EBUSY; + + ret = kstrtouint(page, 0, &val); + if (ret != 0) + return ret; + item->usb.idVendor = val; + return count; +} + +CONFIGFS_ATTR(dovmgr_item_usb_, idProduct); +CONFIGFS_ATTR(dovmgr_item_usb_, idVendor); +#endif + +#if IS_ENABLED(CONFIG_PCI) +static struct configfs_attribute *dovmgr_pci_attrs[] = { + &dovmgr_item_attr_path, + &dovmgr_item_attr_status, + &dovmgr_item_attr_enable, + &dovmgr_item_attr_overlay, + &dovmgr_item_pci_attr_device, + &dovmgr_item_pci_attr_vendor, + &dovmgr_item_pci_attr_subdevice, + &dovmgr_item_pci_attr_subvendor, + &dovmgr_item_pci_attr_class, + &dovmgr_item_pci_attr_class_mask, + NULL, +}; +#endif + +#if IS_ENABLED(CONFIG_USB) +static struct configfs_attribute *dovmgr_usb_attrs[] = { + &dovmgr_item_attr_path, + &dovmgr_item_attr_enable, + &dovmgr_item_attr_status, + &dovmgr_item_attr_overlay, + &dovmgr_item_usb_attr_idVendor, + &dovmgr_item_usb_attr_idProduct, + NULL, +}; +#endif + +static void dovmgr_release(struct config_item *cfsitem) +{ + struct dovmgr_item *item = to_dovmgr_item(cfsitem); + + /* disable item (this removes the overlay and all) */ + dovmgr_item_set_enable(item, false); + + kfree(item->path); + kfree(item); +} + +static struct configfs_item_operations dovmgr_item_ops = { + .release = dovmgr_release, +}; + +#if IS_ENABLED(CONFIG_PCI) +static struct config_item_type dovmgr_pci_item_type = { + .ct_item_ops = &dovmgr_item_ops, + .ct_attrs = dovmgr_pci_attrs, + .ct_owner = THIS_MODULE, +}; +#endif + +#if IS_ENABLED(CONFIG_USB) +static struct config_item_type dovmgr_usb_item_type = { + .ct_item_ops = &dovmgr_item_ops, + .ct_attrs = dovmgr_usb_attrs, + .ct_owner = THIS_MODULE, +}; +#endif + +static struct config_item *dovmgr_group_make_item( + struct config_group *group, const char *name, + enum dovmgr_type type) +{ + struct dovmgr_item *item; + struct config_item_type *item_type; + + switch (type) { +#if IS_ENABLED(CONFIG_PCI) + case ITEM_PCI: + item_type = &dovmgr_pci_item_type; + break; +#endif +#if IS_ENABLED(CONFIG_USB) + case ITEM_USB: + item_type = &dovmgr_usb_item_type; + break; +#endif + default: + return ERR_PTR(-EINVAL); + }; + + item = kzalloc(sizeof(*item), GFP_KERNEL); + if (!item) + return ERR_PTR(-ENOMEM); + + item->type = type; + item->enable = false; + mutex_init(&item->dev_item_mutex); + INIT_LIST_HEAD(&item->dev_item_list); + + switch (type) { +#if IS_ENABLED(CONFIG_PCI) + case ITEM_PCI: + /* default for matching device/vendor */ + item->pci.vendor = PCI_ANY_ID; + item->pci.device = PCI_ANY_ID; + item->pci.subvendor = PCI_ANY_ID; + item->pci.subdevice = PCI_ANY_ID; + item->pci.class = 0; + item->pci.class_mask = 0; + break; +#endif +#if IS_ENABLED(CONFIG_USB) + case ITEM_USB: + /* default */ + item->usb.match_flags = USB_DEVICE_ID_MATCH_DEVICE; + break; +#endif + default: + return ERR_PTR(-EINVAL); + }; + + config_item_init_type_name(&item->item, name, item_type); + return &item->item; +} + +#if IS_ENABLED(CONFIG_PCI) +static struct config_item *dovmgr_group_pci_make_item( + struct config_group *group, const char *name) +{ + return dovmgr_group_make_item(group, name, ITEM_PCI); +} +#endif + +#if IS_ENABLED(CONFIG_USB) +static struct config_item *dovmgr_group_usb_make_item( + struct config_group *group, const char *name) +{ + return dovmgr_group_make_item(group, name, ITEM_USB); +} +#endif + +static void dovmgr_group_drop_item(struct config_group *group, + struct config_item *cfsitem) +{ + struct dovmgr_item *item = to_dovmgr_item(cfsitem); + + switch (item->type) { +#if IS_ENABLED(CONFIG_PCI) + case ITEM_PCI: + break; +#endif +#if IS_ENABLED(CONFIG_USB) + case ITEM_USB: + break; +#endif + default: + break; + } + config_item_put(&item->item); +} + +#if IS_ENABLED(CONFIG_PCI) +static struct configfs_group_operations dovmgr_pci_group_ops = { + .make_item = dovmgr_group_pci_make_item, + .drop_item = dovmgr_group_drop_item, +}; + +static struct config_item_type dovmgr_pci_type = { + .ct_group_ops = &dovmgr_pci_group_ops, + .ct_owner = THIS_MODULE, +}; +#endif + +#if IS_ENABLED(CONFIG_USB) +static struct configfs_group_operations dovmgr_usb_group_ops = { + .make_item = dovmgr_group_usb_make_item, + .drop_item = dovmgr_group_drop_item, +}; + +static struct config_item_type dovmgr_usb_type = { + .ct_group_ops = &dovmgr_usb_group_ops, + .ct_owner = THIS_MODULE, +}; +#endif + +static struct configfs_group_operations dovmgr_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type dovmgr_type = { + .ct_group_ops = &dovmgr_ops, + .ct_owner = THIS_MODULE, +}; + +struct config_group *dovmgr_def_groups[] = { +#if IS_ENABLED(CONFIG_PCI) + &dovmgr_pci_group, +#endif +#if IS_ENABLED(CONFIG_USB) + &dovmgr_usb_group, +#endif + NULL +}; + +static struct configfs_subsystem dovmgr_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "dovmgr", + .ci_type = &dovmgr_type, + }, + .default_groups = dovmgr_def_groups, + }, + .su_mutex = __MUTEX_INITIALIZER(dovmgr_subsys.su_mutex), +}; + +#if IS_ENABLED(CONFIG_PCI) +static int pci_dev_instantiate(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct device *bus_dev; + struct of_changeset cset; + struct device_node *np, *npb; + int ret; + + npb = NULL; + + /* already instantiated */ + if (dev->of_node) { + pr_debug("%s: dev=%s of_node=%s\n", __func__, + kobject_name(&dev->kobj), + of_node_full_name(dev->of_node)); + return 0; + } + + bus_dev = &pdev->bus->dev; + + pr_debug("%s: %s: %02x:%02x.%02x - node %s%s\n", __func__, + kobject_name(&dev->kobj), + pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), + bus_dev->of_node ? of_node_full_name(bus_dev->of_node) : "", + pci_is_bridge(pdev) ? " bridge" : ""); + + /* to create the node, the bus must be present */ + if (!bus_dev->of_node) { + pr_err("%s: No node for %s because no bus device node\n", + __func__, kobject_name(&dev->kobj)); + return 0; + } + + of_changeset_init(&cset); + + np = of_changeset_create_device_node(&cset, bus_dev->of_node, + "%s/pci-%04x-%02x-%02x.%d", + of_node_full_name(bus_dev->of_node), + pci_domain_nr(pdev->bus), pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + if (IS_ERR(np)) { + ret = PTR_ERR(np); + goto out_cset_fail; + } + + ret = of_changeset_add_property_stringf(&cset, np, "compatible", + "pciclass,%04x", (pdev->class >> 8) & 0xffffff); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_u32(&cset, np, "vendor", + pdev->vendor); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_u32(&cset, np, "device", + pdev->device); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_string(&cset, np, "status", "okay"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_bool(&cset, np, "auto-generated"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_attach_node(&cset, np); + if (ret != 0) + goto out_cset_fail; + + /* are we creating a bridge; swell */ + npb = NULL; + if (pci_is_bridge(pdev) && !pdev->subordinate->dev.of_node) { + + pr_debug("%s: %s: bus->dev=%s subordinate=%s\n", __func__, + kobject_name(&dev->kobj), + kobject_name(&pdev->bus->dev.kobj), + kobject_name(&pdev->subordinate->dev.kobj)); + + npb = of_changeset_create_device_node(&cset, bus_dev->of_node, + "%s/pci-%04x-%02x", + of_node_full_name(bus_dev->of_node), + pci_domain_nr(pdev->subordinate), + pdev->subordinate->number); + if (IS_ERR(npb)) { + ret = PTR_ERR(npb); + goto out_cset_fail; + } + + ret = of_changeset_add_property_string(&cset, npb, "compatible", "generic,pci-bus"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_string(&cset, npb, "device_type", "pci"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_string(&cset, npb, "status", "okay"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_add_property_bool(&cset, npb, "auto-generated"); + if (ret != 0) + goto out_cset_fail; + + ret = of_changeset_attach_node(&cset, npb); + if (ret != 0) + goto out_cset_fail; + } + + ret = __of_changeset_apply(&cset); + if (ret != 0) + goto out_cset_fail; + + /* permanently commit */ + of_changeset_destroy(&cset); + + /* bind the node to the device */ + dev->of_node = np; + ret = sysfs_create_link(&dev->kobj, &dev->of_node->kobj, + "of_node"); + if (ret) + pr_warn("%s: %s Error %d creating of_node link\n", + __func__, kobject_name(&dev->kobj), ret); + + if (npb) { + pdev->subordinate->dev.of_node = npb; + ret = sysfs_create_link(&pdev->subordinate->dev.kobj, &npb->kobj, + "of_node"); + if (ret) + pr_warn("%s: %s Error %d creating of_node link\n", + __func__, kobject_name(&dev->kobj), ret); + } + + + return 0; + +out_cset_fail: + pr_err("%s: %s Failed to apply changeset (err=%d)\n", __func__, + kobject_name(&dev->kobj), ret); + of_changeset_destroy(&cset); + return ret; +} + +static int pci_dev_uninstantiate(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np, *npb; + struct of_changeset cset; + int ret; + + /* device node must exist */ + np = dev->of_node; + if (!np) + return 0; + + /* and the auto-generated property */ + if (!of_property_read_bool(np, "auto-generated")) + return 0; + + of_changeset_init(&cset); + + ret = of_changeset_detach_node(&cset, np); + if (ret != 0) + goto out_cset_fail; + + npb = NULL; + if (pci_is_bridge(pdev)) + npb = pdev->subordinate->dev.of_node; + + if (npb != NULL) { + ret = of_changeset_detach_node(&cset, npb); + if (ret != 0) + goto out_cset_fail; + } + + ret = __of_changeset_apply(&cset); + if (ret != 0) + goto out_cset_fail; + + dev->of_node = NULL; + if (npb != NULL) + pdev->subordinate->dev.of_node = NULL; + + pr_debug("%s: %s: %02x:%02x.%02x\n", __func__, + kobject_name(&dev->kobj), + pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + + /* TODO iterate over the properties and free */ + + return 0; + +out_cset_fail: + of_changeset_destroy(&cset); + + return ret; +} + +static int dovmgr_pci_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + int ret; + + if (action == BUS_NOTIFY_ADD_DEVICE) + pci_dev_instantiate(to_pci_dev(arg)); + + ret = dovmgr_notifier_action(&dovmgr_pci_group, action, arg, + dovmgr_pci_item_match, dovmgr_item_notify); + + if (action == BUS_NOTIFY_REMOVED_DEVICE) + pci_dev_uninstantiate(to_pci_dev(arg)); + + return ret; +} + +static struct notifier_block dovmgr_pci_notifier = { + .notifier_call = dovmgr_pci_notify, +}; + +static int pci_instantiate_iterator(struct device *dev, void *data) +{ + return pci_dev_instantiate(to_pci_dev(dev)); +} + +static int dovmgr_pci_init(void) +{ + int ret; + + config_group_init_type_name(&dovmgr_pci_group, "pci", &dovmgr_pci_type); + ret = bus_register_notifier(&pci_bus_type, &dovmgr_pci_notifier); + if (ret != 0) { + pr_err("%s: bus_register_notifier() failed\n", __func__); + return ret; + } + + ret = bus_for_each_dev(&pci_bus_type, NULL, NULL, + pci_instantiate_iterator); + if (ret != 0) { + pr_err("%s: bus_for_each_dev() failed\n", __func__); + return ret; + } + + return 0; +} + +static void dovmgr_pci_cleanup(void) +{ + bus_unregister_notifier(&pci_bus_type, &dovmgr_pci_notifier); +} +#endif + +#if IS_ENABLED(CONFIG_USB) +static int dovmgr_usb_notify(struct notifier_block *nb, + unsigned long action, void *arg) +{ + return dovmgr_notifier_action(&dovmgr_usb_group, action, arg, + dovmgr_usb_item_match, dovmgr_item_notify); +} + +static struct notifier_block dovmgr_usb_notifier = { + .notifier_call = dovmgr_usb_notify, +}; + +static int dovmgr_usb_init(void) +{ + int ret; + + config_group_init_type_name(&dovmgr_usb_group, "usb", &dovmgr_usb_type); + ret = bus_register_notifier(&usb_bus_type, &dovmgr_usb_notifier); + if (ret != 0) { + pr_err("%s: bus_register_notifier() failed\n", __func__); + return ret; + } + return 0; +} + +static void dovmgr_usb_cleanup(void) +{ + bus_unregister_notifier(&usb_bus_type, &dovmgr_usb_notifier); +} +#endif + +static int __init dovmgr_init(void) +{ + int ret; + + config_group_init(&dovmgr_subsys.su_group); + +#if IS_ENABLED(CONFIG_PCI) + ret = dovmgr_pci_init(); + if (ret != 0) + goto err_no_pci_init; +#endif +#if IS_ENABLED(CONFIG_USB) + ret = dovmgr_usb_init(); + if (ret != 0) + goto err_no_usb_init; +#endif + + ret = configfs_register_subsystem(&dovmgr_subsys); + if (ret != 0) { + pr_err("%s: failed to register subsys\n", __func__); + goto err_no_configfs; + } + pr_info("%s: OK\n", __func__); + return 0; + +err_no_configfs: +#if IS_ENABLED(CONFIG_USB) + dovmgr_usb_cleanup(); +err_no_usb_init: +#endif +#if IS_ENABLED(CONFIG_PCI) + dovmgr_pci_cleanup(); +err_no_pci_init: +#endif + return ret; +} +late_initcall(dovmgr_init); diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 04f2e1f..cfc493c 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -3,6 +3,8 @@ menu "EEPROM support" config EEPROM_AT24 tristate "I2C EEPROMs / RAMs / ROMs from most vendors" depends on I2C && SYSFS + select REGMAP + select NVMEM help Enable this driver to get read/write support to most I2C EEPROMs and compatible devices like FRAMs, SRAMs, ROMs etc. After you @@ -30,6 +32,8 @@ config EEPROM_AT24 config EEPROM_AT25 tristate "SPI EEPROMs from most vendors" depends on SPI && SYSFS + select REGMAP + select NVMEM help Enable this driver to get read/write support to most SPI EEPROMs, after you configure the board init code to know about each eeprom @@ -74,6 +78,8 @@ config EEPROM_93CX6 config EEPROM_93XX46 tristate "Microwire EEPROM 93XX46 support" depends on SPI && SYSFS + select REGMAP + select NVMEM help Driver for the microwire EEPROM chipsets 93xx46x. The driver supports both read and write commands and also the command to diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 5d7c090..089d694 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,8 @@ #include #include #include +#include +#include #include /* @@ -55,7 +56,6 @@ struct at24_data { struct at24_platform_data chip; - struct memory_accessor macc; int use_smbus; int use_smbus_write; @@ -64,12 +64,15 @@ struct at24_data { * but not from changes by other I2C masters. */ struct mutex lock; - struct bin_attribute bin; u8 *writebuf; unsigned write_max; unsigned num_addresses; + struct regmap_config regmap_config; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + /* * Some chips tie up multiple I2C addresses; dummy devices reserve * them for us, and we'll use them with SMBus calls. @@ -283,17 +286,6 @@ static ssize_t at24_read(struct at24_data *at24, return retval; } -static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct at24_data *at24; - - at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); - return at24_read(at24, buf, off, count); -} - - /* * Note that if the hardware write-protect pin is pulled high, the whole * chip is normally write protected. But there are plenty of product @@ -414,40 +406,49 @@ static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off, return retval; } -static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct at24_data *at24; - - at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); - return at24_write(at24, buf, off, count); -} - /*-------------------------------------------------------------------------*/ /* - * This lets other kernel code access the eeprom data. For example, it - * might hold a board's Ethernet address, or board-specific calibration - * data generated on the manufacturing floor. - */ - -static ssize_t at24_macc_read(struct memory_accessor *macc, char *buf, - off_t offset, size_t count) + * Provide a regmap interface, which is registered with the NVMEM + * framework +*/ +static int at24_regmap_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) { - struct at24_data *at24 = container_of(macc, struct at24_data, macc); + struct at24_data *at24 = context; + off_t offset = *(u32 *)reg; + int err; - return at24_read(at24, buf, offset, count); + err = at24_read(at24, val, offset, val_size); + if (err) + return err; + return 0; } -static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf, - off_t offset, size_t count) +static int at24_regmap_write(void *context, const void *data, size_t count) { - struct at24_data *at24 = container_of(macc, struct at24_data, macc); + struct at24_data *at24 = context; + const char *buf; + u32 offset; + size_t len; + int err; - return at24_write(at24, buf, offset, count); + memcpy(&offset, data, sizeof(offset)); + buf = (const char *)data + sizeof(offset); + len = count - sizeof(offset); + + err = at24_write(at24, buf, offset, len); + if (err) + return err; + return 0; } +static const struct regmap_bus at24_regmap_bus = { + .read = at24_regmap_read, + .write = at24_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + /*-------------------------------------------------------------------------*/ #ifdef CONFIG_OF @@ -481,6 +482,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) struct at24_data *at24; int err; unsigned i, num_addresses; + struct regmap *regmap; if (client->dev.platform_data) { chip = *(struct at24_platform_data *)client->dev.platform_data; @@ -573,29 +575,12 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) at24->chip = chip; at24->num_addresses = num_addresses; - /* - * Export the EEPROM bytes through sysfs, since that's convenient. - * By default, only root should see the data (maybe passwords etc) - */ - sysfs_bin_attr_init(&at24->bin); - at24->bin.attr.name = "eeprom"; - at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR; - at24->bin.read = at24_bin_read; - at24->bin.size = chip.byte_len; - - at24->macc.read = at24_macc_read; - writable = !(chip.flags & AT24_FLAG_READONLY); if (writable) { if (!use_smbus || use_smbus_write) { unsigned write_max = chip.page_size; - at24->macc.write = at24_macc_write; - - at24->bin.write = at24_bin_write; - at24->bin.attr.mode |= S_IWUSR; - if (write_max > io_limit) write_max = io_limit; if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX) @@ -627,14 +612,38 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) } } - err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin); - if (err) + at24->regmap_config.reg_bits = 32; + at24->regmap_config.val_bits = 8; + at24->regmap_config.reg_stride = 1; + at24->regmap_config.max_register = chip.byte_len - 1; + + regmap = devm_regmap_init(&client->dev, &at24_regmap_bus, at24, + &at24->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "regmap init failed\n"); + err = PTR_ERR(regmap); + goto err_clients; + } + + at24->nvmem_config.name = dev_name(&client->dev); + at24->nvmem_config.dev = &client->dev; + at24->nvmem_config.read_only = !writable; + at24->nvmem_config.root_only = true; + at24->nvmem_config.owner = THIS_MODULE; + at24->nvmem_config.compat = true; + at24->nvmem_config.base_dev = &client->dev; + + at24->nvmem = nvmem_register(&at24->nvmem_config); + + if (IS_ERR(at24->nvmem)) { + err = PTR_ERR(at24->nvmem); goto err_clients; + } i2c_set_clientdata(client, at24); - dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n", - at24->bin.size, client->name, + dev_info(&client->dev, "%u byte %s EEPROM, %s, %u bytes/write\n", + chip.byte_len, client->name, writable ? "writable" : "read-only", at24->write_max); if (use_smbus == I2C_SMBUS_WORD_DATA || use_smbus == I2C_SMBUS_BYTE_DATA) { @@ -645,7 +654,7 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) /* export data to kernel code */ if (chip.setup) - chip.setup(&at24->macc, chip.context); + chip.setup(at24->nvmem, chip.context); return 0; @@ -663,7 +672,8 @@ static int at24_remove(struct i2c_client *client) int i; at24 = i2c_get_clientdata(client); - sysfs_remove_bin_file(&client->dev.kobj, &at24->bin); + + nvmem_unregister(at24->nvmem); for (i = 1; i < at24->num_addresses; i++) i2c_unregister_device(at24->client[i]); diff --git a/drivers/misc/eeprom/at25.c b/drivers/misc/eeprom/at25.c index f850ef5..fa36a6e 100644 --- a/drivers/misc/eeprom/at25.c +++ b/drivers/misc/eeprom/at25.c @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include #include @@ -29,11 +31,12 @@ struct at25_data { struct spi_device *spi; - struct memory_accessor mem; struct mutex lock; struct spi_eeprom chip; - struct bin_attribute bin; unsigned addrlen; + struct regmap_config regmap_config; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; }; #define AT25_WREN 0x06 /* latch the write enable */ @@ -77,10 +80,10 @@ at25_ee_read( struct spi_message m; u8 instr; - if (unlikely(offset >= at25->bin.size)) + if (unlikely(offset >= at25->chip.byte_len)) return 0; - if ((offset + count) > at25->bin.size) - count = at25->bin.size - offset; + if ((offset + count) > at25->chip.byte_len) + count = at25->chip.byte_len - offset; if (unlikely(!count)) return count; @@ -131,21 +134,19 @@ at25_ee_read( return status ? status : count; } -static ssize_t -at25_bin_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +static int at25_regmap_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) { - struct device *dev; - struct at25_data *at25; + struct at25_data *at25 = context; + off_t offset = *(u32 *)reg; + int err; - dev = container_of(kobj, struct device, kobj); - at25 = dev_get_drvdata(dev); - - return at25_ee_read(at25, buf, off, count); + err = at25_ee_read(at25, val, offset, val_size); + if (err) + return err; + return 0; } - static ssize_t at25_ee_write(struct at25_data *at25, const char *buf, loff_t off, size_t count) @@ -155,10 +156,10 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off, unsigned buf_size; u8 *bounce; - if (unlikely(off >= at25->bin.size)) + if (unlikely(off >= at25->chip.byte_len)) return -EFBIG; - if ((off + count) > at25->bin.size) - count = at25->bin.size - off; + if ((off + count) > at25->chip.byte_len) + count = at25->chip.byte_len - off; if (unlikely(!count)) return count; @@ -265,39 +266,29 @@ at25_ee_write(struct at25_data *at25, const char *buf, loff_t off, return written ? written : status; } -static ssize_t -at25_bin_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +static int at25_regmap_write(void *context, const void *data, size_t count) { - struct device *dev; - struct at25_data *at25; - - dev = container_of(kobj, struct device, kobj); - at25 = dev_get_drvdata(dev); - - return at25_ee_write(at25, buf, off, count); -} + struct at25_data *at25 = context; + const char *buf; + u32 offset; + size_t len; + int err; -/*-------------------------------------------------------------------------*/ - -/* Let in-kernel code access the eeprom data. */ - -static ssize_t at25_mem_read(struct memory_accessor *mem, char *buf, - off_t offset, size_t count) -{ - struct at25_data *at25 = container_of(mem, struct at25_data, mem); + memcpy(&offset, data, sizeof(offset)); + buf = (const char *)data + sizeof(offset); + len = count - sizeof(offset); - return at25_ee_read(at25, buf, offset, count); + err = at25_ee_write(at25, buf, offset, len); + if (err) + return err; + return 0; } -static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf, - off_t offset, size_t count) -{ - struct at25_data *at25 = container_of(mem, struct at25_data, mem); - - return at25_ee_write(at25, buf, offset, count); -} +static const struct regmap_bus at25_regmap_bus = { + .read = at25_regmap_read, + .write = at25_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; /*-------------------------------------------------------------------------*/ @@ -358,6 +349,7 @@ static int at25_probe(struct spi_device *spi) { struct at25_data *at25 = NULL; struct spi_eeprom chip; + struct regmap *regmap; int err; int sr; int addrlen; @@ -402,40 +394,35 @@ static int at25_probe(struct spi_device *spi) spi_set_drvdata(spi, at25); at25->addrlen = addrlen; - /* Export the EEPROM bytes through sysfs, since that's convenient. - * And maybe to other kernel code; it might hold a board's Ethernet - * address, or board-specific calibration data generated on the - * manufacturing floor. - * - * Default to root-only access to the data; EEPROMs often hold data - * that's sensitive for read and/or write, like ethernet addresses, - * security codes, board-specific manufacturing calibrations, etc. - */ - sysfs_bin_attr_init(&at25->bin); - at25->bin.attr.name = "eeprom"; - at25->bin.attr.mode = S_IRUSR; - at25->bin.read = at25_bin_read; - at25->mem.read = at25_mem_read; - - at25->bin.size = at25->chip.byte_len; - if (!(chip.flags & EE_READONLY)) { - at25->bin.write = at25_bin_write; - at25->bin.attr.mode |= S_IWUSR; - at25->mem.write = at25_mem_write; - } + at25->regmap_config.reg_bits = 32; + at25->regmap_config.val_bits = 8; + at25->regmap_config.reg_stride = 1; + at25->regmap_config.max_register = chip.byte_len - 1; - err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin); - if (err) - return err; - - if (chip.setup) - chip.setup(&at25->mem, chip.context); + regmap = devm_regmap_init(&spi->dev, &at25_regmap_bus, at25, + &at25->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } - dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n", - (at25->bin.size < 1024) - ? at25->bin.size - : (at25->bin.size / 1024), - (at25->bin.size < 1024) ? "Byte" : "KByte", + at25->nvmem_config.name = dev_name(&spi->dev); + at25->nvmem_config.dev = &spi->dev; + at25->nvmem_config.read_only = chip.flags & EE_READONLY; + at25->nvmem_config.root_only = true; + at25->nvmem_config.owner = THIS_MODULE; + at25->nvmem_config.compat = true; + at25->nvmem_config.base_dev = &spi->dev; + + at25->nvmem = nvmem_register(&at25->nvmem_config); + if (IS_ERR(at25->nvmem)) + return PTR_ERR(at25->nvmem); + + dev_info(&spi->dev, "%d %s %s eeprom%s, pagesize %u\n", + (chip.byte_len < 1024) + ? chip.byte_len + : (chip.byte_len / 1024), + (chip.byte_len < 1024) ? "Byte" : "KByte", at25->chip.name, (chip.flags & EE_READONLY) ? " (readonly)" : "", at25->chip.page_size); @@ -447,7 +434,8 @@ static int at25_remove(struct spi_device *spi) struct at25_data *at25; at25 = spi_get_drvdata(spi); - sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin); + nvmem_unregister(at25->nvmem); + return 0; } diff --git a/drivers/misc/eeprom/eeprom.c b/drivers/misc/eeprom/eeprom.c index 7342fd6..3d1d551 100644 --- a/drivers/misc/eeprom/eeprom.c +++ b/drivers/misc/eeprom/eeprom.c @@ -84,7 +84,7 @@ static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { - struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj)); + struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj)); struct eeprom_data *data = i2c_get_clientdata(client); u8 slice; diff --git a/drivers/misc/eeprom/eeprom_93xx46.c b/drivers/misc/eeprom/eeprom_93xx46.c index ff63f05..426fe2f 100644 --- a/drivers/misc/eeprom/eeprom_93xx46.c +++ b/drivers/misc/eeprom/eeprom_93xx46.c @@ -10,12 +10,17 @@ #include #include +#include #include #include #include +#include +#include +#include #include #include -#include +#include +#include #include #define OP_START 0x4 @@ -25,73 +30,111 @@ #define ADDR_ERAL 0x20 #define ADDR_EWEN 0x30 +struct eeprom_93xx46_devtype_data { + unsigned int quirks; +}; + +static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = { + .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | + EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH, +}; + struct eeprom_93xx46_dev { struct spi_device *spi; struct eeprom_93xx46_platform_data *pdata; - struct bin_attribute bin; struct mutex lock; + struct regmap_config regmap_config; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; int addrlen; + int size; }; +static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ; +} + +static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH; +} + static ssize_t -eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +eeprom_93xx46_read(struct eeprom_93xx46_dev *edev, char *buf, + unsigned off, size_t count) { - struct eeprom_93xx46_dev *edev; - struct device *dev; - struct spi_message m; - struct spi_transfer t[2]; - int bits, ret; - u16 cmd_addr; + ssize_t ret = 0; - dev = container_of(kobj, struct device, kobj); - edev = dev_get_drvdata(dev); + if (unlikely(off >= edev->size)) + return 0; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; - cmd_addr = OP_READ << edev->addrlen; + mutex_lock(&edev->lock); - if (edev->addrlen == 7) { - cmd_addr |= off & 0x7f; - bits = 10; - } else { - cmd_addr |= off & 0x3f; - bits = 9; - } + if (edev->pdata->prepare) + edev->pdata->prepare(edev); - dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", - cmd_addr, edev->spi->max_speed_hz); + while (count) { + struct spi_message m; + struct spi_transfer t[2] = { { 0 } }; + u16 cmd_addr = OP_READ << edev->addrlen; + size_t nbytes = count; + int bits; + int err; + + if (edev->addrlen == 7) { + cmd_addr |= off & 0x7f; + bits = 10; + if (has_quirk_single_word_read(edev)) + nbytes = 1; + } else { + cmd_addr |= (off >> 1) & 0x3f; + bits = 9; + if (has_quirk_single_word_read(edev)) + nbytes = 2; + } - spi_message_init(&m); - memset(t, 0, sizeof(t)); + dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", + cmd_addr, edev->spi->max_speed_hz); - t[0].tx_buf = (char *)&cmd_addr; - t[0].len = 2; - t[0].bits_per_word = bits; - spi_message_add_tail(&t[0], &m); + spi_message_init(&m); - t[1].rx_buf = buf; - t[1].len = count; - t[1].bits_per_word = 8; - spi_message_add_tail(&t[1], &m); + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + spi_message_add_tail(&t[0], &m); - mutex_lock(&edev->lock); + t[1].rx_buf = buf; + t[1].len = count; + t[1].bits_per_word = 8; + spi_message_add_tail(&t[1], &m); - if (edev->pdata->prepare) - edev->pdata->prepare(edev); + err = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); - ret = spi_sync(edev->spi, &m); - /* have to wait at least Tcsl ns */ - ndelay(250); - if (ret) { - dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", - count, (int)off, ret); + if (err) { + dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n", + nbytes, (int)off, err); + ret = err; + break; + } + + buf += nbytes; + off += nbytes; + count -= nbytes; + ret += nbytes; } if (edev->pdata->finish) edev->pdata->finish(edev); mutex_unlock(&edev->lock); - return ret ? : count; + return ret; } static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) @@ -110,7 +153,13 @@ static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) bits = 9; } - dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr); + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "ew%s cmd 0x%04x, %d bits\n", + is_on ? "en" : "ds", cmd_addr, bits); spi_message_init(&m); memset(&t, 0, sizeof(t)); @@ -155,7 +204,7 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, bits = 10; data_len = 1; } else { - cmd_addr |= off & 0x3f; + cmd_addr |= (off >> 1) & 0x3f; bits = 9; data_len = 2; } @@ -182,16 +231,17 @@ eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, } static ssize_t -eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) +eeprom_93xx46_write(struct eeprom_93xx46_dev *edev, const char *buf, + loff_t off, size_t count) { - struct eeprom_93xx46_dev *edev; - struct device *dev; int i, ret, step = 1; - dev = container_of(kobj, struct device, kobj); - edev = dev_get_drvdata(dev); + if (unlikely(off >= edev->size)) + return -EFBIG; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; /* only write even number of bytes on 16-bit devices */ if (edev->addrlen == 6) { @@ -228,6 +278,49 @@ eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj, return ret ? : count; } +/* + * Provide a regmap interface, which is registered with the NVMEM + * framework +*/ +static int eeprom_93xx46_regmap_read(void *context, const void *reg, + size_t reg_size, void *val, + size_t val_size) +{ + struct eeprom_93xx46_dev *eeprom_93xx46 = context; + off_t offset = *(u32 *)reg; + int err; + + err = eeprom_93xx46_read(eeprom_93xx46, val, offset, val_size); + if (err) + return err; + return 0; +} + +static int eeprom_93xx46_regmap_write(void *context, const void *data, + size_t count) +{ + struct eeprom_93xx46_dev *eeprom_93xx46 = context; + const char *buf; + u32 offset; + size_t len; + int err; + + memcpy(&offset, data, sizeof(offset)); + buf = (const char *)data + sizeof(offset); + len = count - sizeof(offset); + + err = eeprom_93xx46_write(eeprom_93xx46, buf, offset, len); + if (err) + return err; + return 0; +} + +static const struct regmap_bus eeprom_93xx46_regmap_bus = { + .read = eeprom_93xx46_regmap_read, + .write = eeprom_93xx46_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) { struct eeprom_93xx46_platform_data *pd = edev->pdata; @@ -245,6 +338,13 @@ static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) bits = 9; } + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits); + spi_message_init(&m); memset(&t, 0, sizeof(t)); @@ -294,12 +394,101 @@ static ssize_t eeprom_93xx46_store_erase(struct device *dev, } static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase); +static void select_assert(void *context) +{ + struct eeprom_93xx46_dev *edev = context; + + gpiod_set_value_cansleep(edev->pdata->select, 1); +} + +static void select_deassert(void *context) +{ + struct eeprom_93xx46_dev *edev = context; + + gpiod_set_value_cansleep(edev->pdata->select, 0); +} + +static const struct of_device_id eeprom_93xx46_of_table[] = { + { .compatible = "eeprom-93xx46", }, + { .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, }, + {} +}; +MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table); + +static int eeprom_93xx46_probe_dt(struct spi_device *spi) +{ + const struct of_device_id *of_id = + of_match_device(eeprom_93xx46_of_table, &spi->dev); + struct device_node *np = spi->dev.of_node; + struct eeprom_93xx46_platform_data *pd; + u32 tmp; + int gpio; + enum of_gpio_flags of_flags; + int ret; + + pd = devm_kzalloc(&spi->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + ret = of_property_read_u32(np, "data-size", &tmp); + if (ret < 0) { + dev_err(&spi->dev, "data-size property not found\n"); + return ret; + } + + if (tmp == 8) { + pd->flags |= EE_ADDR8; + } else if (tmp == 16) { + pd->flags |= EE_ADDR16; + } else { + dev_err(&spi->dev, "invalid data-size (%d)\n", tmp); + return -EINVAL; + } + + if (of_property_read_bool(np, "read-only")) + pd->flags |= EE_READONLY; + + gpio = of_get_named_gpio_flags(np, "select-gpios", 0, &of_flags); + if (gpio_is_valid(gpio)) { + unsigned long flags = + of_flags == OF_GPIO_ACTIVE_LOW ? GPIOF_ACTIVE_LOW : 0; + + ret = devm_gpio_request_one(&spi->dev, gpio, flags, + "eeprom_93xx46_select"); + if (ret) + return ret; + + pd->select = gpio_to_desc(gpio); + pd->prepare = select_assert; + pd->finish = select_deassert; + + gpiod_direction_output(pd->select, 0); + } + + if (of_id->data) { + const struct eeprom_93xx46_devtype_data *data = of_id->data; + + pd->quirks = data->quirks; + } + + spi->dev.platform_data = pd; + + return 0; +} + static int eeprom_93xx46_probe(struct spi_device *spi) { struct eeprom_93xx46_platform_data *pd; struct eeprom_93xx46_dev *edev; + struct regmap *regmap; int err; + if (spi->dev.of_node) { + err = eeprom_93xx46_probe_dt(spi); + if (err < 0) + return err; + } + pd = spi->dev.platform_data; if (!pd) { dev_err(&spi->dev, "missing platform data\n"); @@ -325,19 +514,34 @@ static int eeprom_93xx46_probe(struct spi_device *spi) edev->spi = spi_dev_get(spi); edev->pdata = pd; - sysfs_bin_attr_init(&edev->bin); - edev->bin.attr.name = "eeprom"; - edev->bin.attr.mode = S_IRUSR; - edev->bin.read = eeprom_93xx46_bin_read; - edev->bin.size = 128; - if (!(pd->flags & EE_READONLY)) { - edev->bin.write = eeprom_93xx46_bin_write; - edev->bin.attr.mode |= S_IWUSR; + edev->size = 128; + + edev->regmap_config.reg_bits = 32; + edev->regmap_config.val_bits = 8; + edev->regmap_config.reg_stride = 1; + edev->regmap_config.max_register = edev->size - 1; + + regmap = devm_regmap_init(&spi->dev, &eeprom_93xx46_regmap_bus, edev, + &edev->regmap_config); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "regmap init failed\n"); + err = PTR_ERR(regmap); + goto fail; } - err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin); - if (err) + edev->nvmem_config.name = dev_name(&spi->dev); + edev->nvmem_config.dev = &spi->dev; + edev->nvmem_config.read_only = pd->flags & EE_READONLY; + edev->nvmem_config.root_only = true; + edev->nvmem_config.owner = THIS_MODULE; + edev->nvmem_config.compat = true; + edev->nvmem_config.base_dev = &spi->dev; + + edev->nvmem = nvmem_register(&edev->nvmem_config); + if (IS_ERR(edev->nvmem)) { + err = PTR_ERR(edev->nvmem); goto fail; + } dev_info(&spi->dev, "%d-bit eeprom %s\n", (pd->flags & EE_ADDR8) ? 8 : 16, @@ -359,10 +563,11 @@ static int eeprom_93xx46_remove(struct spi_device *spi) { struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); + nvmem_unregister(edev->nvmem); + if (!(edev->pdata->flags & EE_READONLY)) device_remove_file(&spi->dev, &dev_attr_erase); - sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin); kfree(edev); return 0; } @@ -370,6 +575,7 @@ static int eeprom_93xx46_remove(struct spi_device *spi) static struct spi_driver eeprom_93xx46_driver = { .driver = { .name = "93xx46", + .of_match_table = of_match_ptr(eeprom_93xx46_of_table), }, .probe = eeprom_93xx46_probe, .remove = eeprom_93xx46_remove, diff --git b/drivers/misc/tieqep.c b/drivers/misc/tieqep.c new file mode 100644 index 0000000..a0652f9 --- /dev/null +++ b/drivers/misc/tieqep.c @@ -0,0 +1,766 @@ +/* + * TI eQEP driver for AM33xx devices + * + * Copyright (C) 2013 Nathaniel R. Lewis - http://teknoman117.wordpress.com/ + * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * sysfs entries + * - position = absolute - current position; relative - last latched value + * - mode => 0 - absolute; 1 - relative + * - period => sampling period for the hardware + * - enable => 0 - eQEP disabled, 1 - eQEP enabled + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Include the PWMSS subsystem headers, because this unit controls + * whether our clock source is enabled, and if eQEP is to be + * enabled, we need to start the clock + */ +#include "../pwm/pwm-tipwmss.h" + + +/* eQEP register offsets from its base IO address */ +#define QPOSCNT 0x0000 +#define QPOSINIT 0x0004 +#define QPOSMAX 0x0008 +#define QPOSCMP 0x000C +#define QPOSILAT 0x0010 +#define QPOSSLAT 0x0014 +#define QPOSLAT 0x0018 +#define QUTMR 0x001C +#define QUPRD 0x0020 +#define QWDTMR 0x0024 +#define QWDPRD 0x0026 +#define QDECCTL 0x0028 +#define QEPCTL 0x002A +#define QCAPCTL 0x002C +#define QPOSCTL 0x002E +#define QEINT 0x0030 +#define QFLG 0x0032 +#define QCLR 0x0034 +#define QFRC 0x0036 +#define QEPSTS 0x0038 +#define QCTMR 0x003A +#define QCPRD 0x003C +#define QCTMRLAT 0x003E +#define QCPRDLAT 0x0040 +#define QREVID 0x005C + +#if 0 /* if you wanted another way to modify IP registers... */ +typedef volatile u32 REG32; +typedef volatile u16 REG16; +struct EQEP_REGS { + REG32 q_poscnt; /* 0x00 position counter */ + REG32 q_posinit; /* 0x04 position counter initialization */ + REG32 q_posmax; /* 0x08 maximum position count */ + REG32 q_poscmp; /* 0x0C position compare */ + REG32 q_posilat; /* 0x10 index position latch */ + REG32 q_posslat; /* 0x14 strobe position latch */ + REG32 q_poslat; /* 0x18 position counter latch */ + REG32 q_utmr; /* 0x1C unit timer */ + REG32 q_uprd; /* 0x20 unit period */ + REG16 q_wdtmr; /* 0x24 watchdog timer */ + REG16 q_wdprd; /* 0x26 watchdog period */ + REG16 q_decctl; /* 0x28 decoder control */ + REG16 q_epctl; /* 0x2A control register */ + REG16 q_capctl; /* 0x2C capture control */ + REG16 q_posctl; /* 0x2E position compare control */ + REG16 q_eint; /* 0x30 interrupt enable */ + REG16 q_flg; /* 0x32 interrupt flag */ + REG16 q_clr; /* 0x34 interrupt clear */ + REG16 q_frc; /* 0x36 interrupt force */ + REG16 q_epsts; /* 0x38 status */ + REG16 q_ctmr; /* 0x3A capture timer */ + REG16 q_cprd; /* 0x3C capture period */ + REG16 q_ctmrlat; /* 0x3E capture timer latch */ + REG16 q_prdlat; /* 0x40 capture period latch */ + char q_fill1[0x5c-0x40]; + REG32 q_revid; /* 0x5C revision id */ +}; +#endif + + +/* Bits for the QDECTL register */ +#define QSRC1 (1 << 15) +#define QSRC0 (1 << 14) +#define SOEN (1 << 13) +#define SPSEL (1 << 12) +#define XCR (1 << 11) +#define SWAP (1 << 10) +#define IGATE (1 << 9) +#define QAP (1 << 8) +#define QBP (1 << 7) +#define QIP (1 << 6) +#define QSP (1 << 5) + +/* Bits for the QEPCTL register */ +#define FREESOFT1 (1 << 15) +#define FREESOFT0 (1 << 14) +#define PCRM1 (1 << 13) +#define PCRM0 (1 << 12) +#define SEI1 (1 << 11) +#define SEI0 (1 << 10) +#define IEI1 (1 << 9) +#define IEI0 (1 << 8) +#define SWI (1 << 7) +#define SEL (1 << 6) +#define IEL1 (1 << 5) +#define IEL0 (1 << 4) +#define PHEN (1 << 3) +#define QCLM (1 << 2) +#define UTE (1 << 1) +#define WDE (1 << 0) + +/* Bits for the QCAPCTL register */ +#define CEN (1 << 15) +#define CCPS2 (1 << 6) +#define CCPS0 (1 << 5) +#define CCPS1 (1 << 4) +#define UPPS3 (1 << 3) +#define UPPS2 (1 << 2) +#define UPPS1 (1 << 1) +#define UPPS0 (1 << 0) + +/* Bits for the QPOSCTL register */ +#define PCSHDW (1 << 15) +#define PCLOAD (1 << 14) +#define PCPOL (1 << 13) +#define PCE (1 << 12) +#define PCSPW11 (1 << 11) +#define PCSPW10 (1 << 10) +#define PCSPW9 (1 << 9) +#define PCSPW8 (1 << 8) +#define PCSPW7 (1 << 7) +#define PCSPW6 (1 << 6) +#define PCSPW5 (1 << 5) +#define PCSPW4 (1 << 4) +#define PCSPW3 (1 << 3) +#define PCSPW2 (1 << 2) +#define PCSPW1 (1 << 1) +#define PCSPW0 (1 << 0) + +/* Bits for the interrupt registers */ +#define EQEP_INTERRUPT_MASK 0x0FFF +#define UTOF (1 << 11) + +/* Bits to control the clock in the PWMSS subsystem */ +#define PWMSS_EQEPCLK_EN BIT(4) +#define PWMSS_EQEPCLK_STOP_REQ BIT(5) +#define PWMSS_EQEPCLK_EN_ACK BIT(4) + +/* + * Modes for the eQEP unit + * Absolute - the position entry represents the current position of the encoder. + * Poll this value and it will be notified every period nanoseconds + * Relative - the position entry represents the last latched position of the encoder + * This value is latched every period nanoseconds and the internal counter + * is subsequenty reset + */ +#define TIEQEP_MODE_ABSOLUTE 0 +#define TIEQEP_MODE_RELATIVE 1 + +/* Structure defining the characteristics of the eQEP unit */ +struct eqep_chip +{ + /* Platform device for this eQEP unit */ + struct platform_device *pdev; + + /* Pointer to the base of the memory of the eQEP unit */ + void __iomem *mmio_base; + + /* SYSCLKOUT to the eQEP unit */ + u32 clk_rate; + + /* IRQ for the eQEP unit */ + u16 irq; + + /* Mode of the eQEP unit */ + u8 op_mode; + + /* work stuct for the notify userspace work */ + struct work_struct notify_work; + + /* Backup for driver suspension */ + u16 prior_qepctl; + u16 prior_qeint; +}; + +/* Notify userspace work */ +static void notify_handler(struct work_struct *work) +{ + /* Get a reference to the eQEP driver */ + struct eqep_chip *eqep = container_of(work, struct eqep_chip, notify_work); + + /* Notify the userspace */ + sysfs_notify(&eqep->pdev->dev.kobj, NULL, "position"); +} + +/* eQEP Interrupt handler */ +static irqreturn_t eqep_irq_handler(int irq, void *dev_id) +{ + /* Get the instance information */ + struct platform_device *pdev = dev_id; + struct eqep_chip *eqep = platform_get_drvdata(pdev); + + /* Get the interrupt flags */ + u16 iflags = readw(eqep->mmio_base + QFLG) & EQEP_INTERRUPT_MASK; + + /* Check the interrupt source(s) */ + if (iflags & UTOF) { + /* Handle the unit timer overflow interrupt by notifying any potential pollers */ + schedule_work(&eqep->notify_work); + } + + /* Clear interrupt flags (write back triggered flags to the clear register) */ + writew(iflags, eqep->mmio_base + QCLR); + + /* Return that the IRQ was handled successfully */ + return IRQ_HANDLED; +} + +/* Function to read whether the eQEP unit is enabled or disabled */ +static ssize_t eqep_get_enabled(struct device *dev, struct device_attribute *attr, char *buf) +{ + /* Get the instance structure */ + struct eqep_chip *eqep = dev_get_drvdata(dev); + + /* Read the qep control register and mask all but the enabled bit */ + u16 enabled = readw(eqep->mmio_base + QEPCTL) & PHEN; + + /* Return the target in string format */ + return sprintf(buf, "%u\n", (enabled) ? 1 : 0); +} + +/* Function to set if the eQEP is enabled */ +static ssize_t eqep_set_enabled(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + /* Get the instance structure */ + int rc; + u16 val; + u8 enabled; + struct eqep_chip *eqep = dev_get_drvdata(dev); + + /* Convert the input string to an 8 bit uint */ + if ((rc = kstrtou8(buf, 0, &enabled))) + return rc; + + /* Get the existing state of QEPCTL */ + val = readw(eqep->mmio_base + QEPCTL); + + /* If we passed a number that is not 0, enable the eQEP */ + if (enabled) + /* Enable the eQEP (Set PHEN in QEPCTL) */ + val |= PHEN; + else + /* Disable the eQEP (Clear PHEN in QEPCTL) */ + val &= ~PHEN; + + /* Write flags back to control register */ + writew(val, eqep->mmio_base + QEPCTL); + + /* Return buffer length consumed (all) */ + return count; +} + +/* Function to read the current position of the eQEP */ +static ssize_t eqep_get_position(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct eqep_chip *eqep = dev_get_drvdata(dev); + + s32 position = 0; + + if (eqep->op_mode == TIEQEP_MODE_ABSOLUTE) { + position = readl(eqep->mmio_base + QPOSCNT); + } else if (eqep->op_mode == TIEQEP_MODE_RELATIVE) { + /* in relative mode, use the last latched value of the eQEP hardware */ + position = readl(eqep->mmio_base + QPOSLAT); + dev_dbg(dev, "get_position:0x%08x\n", position); + } + + return sprintf(buf, "%d\n", position); +} + +/* Function to set the position of the eQEP hardware */ +static ssize_t eqep_set_position(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + s32 position; + struct eqep_chip *eqep = dev_get_drvdata(dev); + + if ((rc = kstrtos32(buf, 0, &position))) + return rc; + + /* + * If we are in absolute mode, set the position of the encoder, + * discard relative mode because thats pointless + */ + if (eqep->op_mode == TIEQEP_MODE_ABSOLUTE) { + /* If absolute mode, set the current value of the eQEP hardware */ + writel(position, eqep->mmio_base + QPOSCNT); + } + + /* Return buffer length consumed (all) */ + return count; +} + +/* Function to read the period of the unit time event timer */ +static ssize_t eqep_get_timer_period(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct eqep_chip *eqep = dev_get_drvdata(dev); + u64 period; + + /* Convert from counts per interrupt back into period_ns */ + period = readl(eqep->mmio_base + QUPRD); + period = period * NSEC_PER_SEC; + do_div(period, eqep->clk_rate); + + /* Otherwise write out the data */ + return sprintf(buf, "%llu\n", period); +} + +/* Function to set the unit timer period. 0 = off, greater than zero sets the period */ +static ssize_t eqep_set_timer_period(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + u16 tmp; + u64 period; + + struct eqep_chip *eqep = dev_get_drvdata(dev); + + if ((rc = kstrtou64(buf, 0, &period))) + return rc; + + /* Disable the unit timer before modifying its period register */ + tmp = readw(eqep->mmio_base + QEPCTL); + tmp &= ~(UTE | QCLM); + writew(tmp, eqep->mmio_base + QEPCTL); + + /* Zero the unit timer counter register */ + writel(0, eqep->mmio_base + QUTMR); + + /* If the timer is enabled (a non-zero period has been passed) */ + if (period) { + /* update the period */ + period = period * eqep->clk_rate; + do_div(period, NSEC_PER_SEC); + + dev_dbg(dev, "eqep_set_timer_period:%llu\n", period); + + writel(period, eqep->mmio_base + QUPRD); + + /* Enable unit timer, and latch QPOSLAT to QPOSCNT on timer expiration */ + tmp |= UTE | QCLM; + writew(tmp, eqep->mmio_base + QEPCTL); + } + + return count; +} + +/* Function to read the mode of the eQEP hardware */ +static ssize_t eqep_get_mode(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct eqep_chip *eqep = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", eqep->op_mode); +} + +/* Function to set the mode of the eQEP hardware */ +static ssize_t eqep_set_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int rc; + u16 val; + u8 tmp_mode; + struct eqep_chip *eqep = dev_get_drvdata(dev); + + if ((rc = kstrtou8(buf, 0, &tmp_mode))) + return rc; + + dev_dbg(dev, "eqep_set_mode:%d\n", tmp_mode); + + val = readw(eqep->mmio_base + QEPCTL); + + if (tmp_mode == TIEQEP_MODE_ABSOLUTE) { + /* + * In absolute mode, don't reset the hardware based on time, + * so disable the unit timer position reset (Set PCRM[1:0] = 0) + */ + val &= ~(PCRM1 | PCRM0); + + eqep->op_mode = TIEQEP_MODE_ABSOLUTE; + } else if (tmp_mode == TIEQEP_MODE_RELATIVE) { + /* + * In relative mode, latch the value of the eQEP hardware on the + * overflow of the unit timer. So enable the unit timer position reset + * (Set PCRM[1:0] = 3) + */ + val |= PCRM1 | PCRM0; + + eqep->op_mode = TIEQEP_MODE_RELATIVE; + } + + writew(val, eqep->mmio_base + QEPCTL); + + return count; +} + +/* Bind read/write functions to sysfs entries */ +static DEVICE_ATTR(enabled, 0644, eqep_get_enabled, eqep_set_enabled); +static DEVICE_ATTR(position, 0644, eqep_get_position, eqep_set_position); +static DEVICE_ATTR(period, 0644, eqep_get_timer_period, eqep_set_timer_period); +static DEVICE_ATTR(mode, 0644, eqep_get_mode, eqep_set_mode); + +/* Array holding all of the sysfs entries */ +static const struct attribute *eqep_attrs[] = { + &dev_attr_enabled.attr, + &dev_attr_position.attr, + &dev_attr_period.attr, + &dev_attr_mode.attr, + NULL, +}; + +/* Driver function group */ +static const struct attribute_group eqep_device_attr_group = { + .attrs = (struct attribute **) eqep_attrs, +}; + +/* Driver compatibility list */ +static struct of_device_id eqep_of_match[] = +{ + { .compatible = "ti,am33xx-eqep" }, + { } +}; + +/* Register our compatibilities for device trees */ +MODULE_DEVICE_TABLE(of, eqep_of_match); + +/* Create an instance of the eQEP driver */ +static int eqep_probe(struct platform_device *pdev) +{ + struct resource *r; + struct clk *clk; + struct eqep_chip *eqep; + struct pinctrl *pinctrl; + + int ret; + u64 period; + u16 status; + u32 value; + + dev_info(&pdev->dev, "ver. 1.0\n"); + + /* Select pins provided through the device tree */ + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); + if (IS_ERR(pinctrl)) + { + dev_warn(&pdev->dev, "unable to select pin group\n"); + } + + /* Allocate a eqep_driver object */ + eqep = devm_kzalloc(&pdev->dev, sizeof(struct eqep_chip), GFP_KERNEL); + if (!eqep) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + /* Get a handle to the system clock object */ + clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(clk); + } + + /* Get the frequency of the system clock */ + eqep->clk_rate = clk_get_rate(clk); + if (!eqep->clk_rate) { + dev_err(&pdev->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + /* Get a resource containing the IRQ for this eQEP controller */ + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (unlikely(!r)) { + dev_err(&pdev->dev, "Invalid IRQ resource\n"); + return -ENODEV; + } + + /* Store the irq */ + eqep->irq = r->start; + + /* Get a resource containing the requested (from DT) memory address and range of eQEP controller */ + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + /* Remap the eQEP controller memory into our own memory space */ + eqep->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(eqep->mmio_base)) + return PTR_ERR(eqep->mmio_base); + + /* Store the platform device in our eQEP data structure for later usage */ + eqep->pdev = pdev; + + /* Subscribe to the eQEP interrupt */ + if (request_irq(eqep->irq, eqep_irq_handler, IRQF_IRQPOLL, "eqep_interrupt", pdev)) + { + dev_err(&pdev->dev, "unable to request irq for eQEP\n"); + return -ENODEV; + } + + /* Register controls to sysfs */ + if (sysfs_create_group(&pdev->dev.kobj, &eqep_device_attr_group)) + { + dev_err(&pdev->dev, "sysfs creation failed\n"); + return -EINVAL; + } + + /* set QDECCTL */ + status = 0; /* default to Quadrature count mode, QSRC1 & QSRC0 = 0 */ + + /* set QSRC1 & QSRC0 bits, one of 4 count_modes. */ + if (!of_property_read_u32(pdev->dev.of_node, "count_mode", &value) && value <= 3) { + status |= value << 14; + + /* + * in count up or count down mode, count on rising edge only + * not on both edges. + */ + if (value >= 2) + status |= XCR; + } + dev_dbg(&pdev->dev, "count_mode:%d\n", value); + + /* Should we invert the qa input */ + if (!of_property_read_u32(pdev->dev.of_node, "invert_qa", &value)) + status = value ? status | QAP : status & ~QAP; + dev_dbg(&pdev->dev, "invert_qa:%d\n", value); + + /* Should we invert the qb input */ + if (!of_property_read_u32(pdev->dev.of_node, "invert_qb", &value)) + status = value ? status | QBP : status & ~QBP; + dev_dbg(&pdev->dev, "invert_qb:%d\n", value); + + /* Should we invert the index input */ + if (!of_property_read_u32(pdev->dev.of_node, "invert_qi", &value)) + status = value ? status | QIP : status & ~QIP; + dev_dbg(&pdev->dev, "invert_qi:%d\n", value); + + /* Should we invert the strobe input */ + if (!of_property_read_u32(pdev->dev.of_node, "invert_qs", &value)) + status = value ? status | QSP : status & ~QSP; + dev_dbg(&pdev->dev, "invert_qs:%d\n", value); + + /* Should we swap the cha and chb inputs */ + if (!of_property_read_u32(pdev->dev.of_node, "swap_inputs", &value)) + status = value ? status | SWAP : status & ~SWAP; + dev_dbg(&pdev->dev, "swap_inputs:%d\n", value); + + dev_dbg(&pdev->dev, "QDECCTL:0x%04x\n", status); + + /* Write the decoder control settings back to the control register */ + writew(status, eqep->mmio_base + QDECCTL); + + writel( 0, eqep->mmio_base + QPOSINIT); + writel(~0, eqep->mmio_base + QPOSMAX); + writel( 0, eqep->mmio_base + QPOSCNT); + + dev_dbg(&pdev->dev, "QPOSINIT:0x%08x\n", readl(eqep->mmio_base + QPOSINIT)); + dev_dbg(&pdev->dev, "QPOSMAX:0x%08x\n", readl(eqep->mmio_base + QPOSMAX)); + dev_dbg(&pdev->dev, "QPOSCNT:0x%08x\n", readl(eqep->mmio_base + QPOSCNT)); + + status = UTOF; /* Enable Unit Time Period interrupt. */ + if (!of_property_read_u32(pdev->dev.of_node, "omit_interrupt", &value) && value) { + status = 0; /* no interrupt */ + } + writew(status, eqep->mmio_base + QEINT); + dev_dbg(&pdev->dev, "omit_interrupt:%d\n", value); + dev_dbg(&pdev->dev, "QEINT:0x%04x\n", status); + + /* Calculate the timer ticks per second */ + period = 1000000000; + period = period * eqep->clk_rate; + do_div(period, NSEC_PER_SEC); + + /* Set this period into the unit timer period register */ + writel(period, eqep->mmio_base + QUPRD); + dev_dbg(&pdev->dev, "QUPRD:0x%08x\n", (u32) period); + + /* + * Enable the eQEP with basic position counting turned on + * PHEN - Quadrature position counter enable bit + * UTE - unit timer enable + * QCLM - latch QPOSLAT to QPOSCNT upon unit timer overflow + * IEL0 - Latch QPOSILAT on index signal. Rising or falling, IEL[1:0] = 0 is reserved + * SWI - Software initialization of position count register, i.e. set QPOSCNT <= QPOSINIT, + * but this bit was not being reset by hardware as advertised in TRM, + * (so omit & clear QPOSCNT manually elsewhere?) + */ + status = PHEN | UTE | QCLM | IEL0 | SWI; + writew(status, eqep->mmio_base + QEPCTL); + dev_dbg(&pdev->dev, "QEPCTL:0x%04x write\n", status); + dev_dbg(&pdev->dev, "QEPCTL:0x%04x read\n", readw(eqep->mmio_base + QEPCTL)); + + /* We default to absolute mode */ + eqep->op_mode = TIEQEP_MODE_ABSOLUTE; + + /* Enable the power management runtime */ + pm_runtime_enable(&pdev->dev); + + /* Increment the device usage count and run pm_runtime_resume() */ + pm_runtime_get_sync(&pdev->dev); + + /* Enable the clock to the eQEP unit */ + status = pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EQEPCLK_EN); + + /* If we failed to enable the clocks, fail out */ + if (!(status & PWMSS_EQEPCLK_EN_ACK)) { + dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); + ret = -EINVAL; + goto pwmss_clk_failure; + } + + /* Initialize the notify work struture */ + INIT_WORK(&eqep->notify_work, notify_handler); + + /* Decrement the device usage count (twice) and run pm_runtime_idle() if zero */ + pm_runtime_put_sync(&pdev->dev); + + /* Set the platform driver data to the data object we've been creating for the eQEP unit */ + platform_set_drvdata(pdev, eqep); + + /* Success! */ + dev_dbg(&pdev->dev, "irq:%d, clk_rate:%u\n", eqep->irq, eqep->clk_rate); + return 0; + + /* If a failure occurred, stop the runtime power management */ +pwmss_clk_failure: + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return ret; +} + +/* Remove an instance of the eQEP driver */ +static int eqep_remove(struct platform_device *pdev) +{ + /* Get the eQEP driver data from the platform device structure */ + struct eqep_chip *eqep = platform_get_drvdata(pdev); + + /* Cancel work */ + cancel_work_sync(&eqep->notify_work); + + /* Unmap from sysfs */ + sysfs_remove_group(&pdev->dev.kobj, &eqep_device_attr_group); + + /* Release important assets */ + free_irq(eqep->irq, pdev); + + /* Increment the device usage count and run pm_runtime_resume() */ + pm_runtime_get_sync(&pdev->dev); + + /* Disable the eQEP clock */ + pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EQEPCLK_STOP_REQ); + + /* Decrement the device usage count (twice) and run pm_runtime_idle() if zero */ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); + + /* Disable the runtime power management of this device */ + pm_runtime_disable(&pdev->dev); + + /* Return success */ + return 0; +} + +/* Power management suspend device */ +static int eqep_suspend(struct device *dev) +{ + /* Get the eqep driver information */ + struct eqep_chip *eqep = dev_get_drvdata(dev); + u16 tmp; + + /* Shut down interrupts */ + eqep->prior_qeint = readw(eqep->mmio_base + QEINT); + tmp = eqep->prior_qeint & ~UTOF; + writew(tmp, eqep->mmio_base + QEINT); + + /* Get the existing state of QEPCTL */ + eqep->prior_qepctl = readw(eqep->mmio_base + QEPCTL); + + /* Disable eQEP controller */ + writew(eqep->prior_qepctl & ~PHEN, eqep->mmio_base + QEPCTL); + + /* Decrement the device usage count and run pm_runtime_idle() if zero */ + pm_runtime_put_sync(dev); + + /* Return success */ + return 0; +} + +/* Power management wake device back up */ +static int eqep_resume(struct device *dev) +{ + /* Get the eqep driver information */ + struct eqep_chip *eqep = dev_get_drvdata(dev); + + /* Restore interrupt enabled register */ + writew(eqep->prior_qeint, eqep->mmio_base + QEINT); + + /* Restore prior qep control register */ + writew(eqep->prior_qepctl, eqep->mmio_base + QEPCTL); + + /* Increment the device usage count and run pm_runtime_resume() */ + pm_runtime_get_sync(dev); + + /* Success */ + return 0; +} + +/* create pm functions object */ +static SIMPLE_DEV_PM_OPS(eqep_pm_ops, eqep_suspend, eqep_resume); + +/* Platform driver information */ +static struct platform_driver eqep_driver = { + .driver = { + .name = "eqep", + .owner = THIS_MODULE, + .pm = &eqep_pm_ops, + .of_match_table = eqep_of_match, + }, + .probe = eqep_probe, + .remove = eqep_remove, +}; + +/* Register this platform driver */ +module_platform_driver(eqep_driver); + +/* Module information */ +MODULE_DESCRIPTION("TI eQEP driver"); +MODULE_AUTHOR("Nathaniel R. Lewis"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/ethernet/ti/davinci_cpdma.c b/drivers/net/ethernet/ti/davinci_cpdma.c index 657b65b..18bf3a8 100644 --- a/drivers/net/ethernet/ti/davinci_cpdma.c +++ b/drivers/net/ethernet/ti/davinci_cpdma.c @@ -82,7 +82,7 @@ struct cpdma_desc { struct cpdma_desc_pool { phys_addr_t phys; - u32 hw_addr; + dma_addr_t hw_addr; void __iomem *iomap; /* ioremap map */ void *cpumap; /* dma_alloc map */ int desc_size, mem_size; @@ -152,7 +152,7 @@ struct cpdma_chan { * abstract out these details */ static struct cpdma_desc_pool * -cpdma_desc_pool_create(struct device *dev, u32 phys, u32 hw_addr, +cpdma_desc_pool_create(struct device *dev, u32 phys, dma_addr_t hw_addr, int size, int align) { int bitmap_size; @@ -176,13 +176,13 @@ cpdma_desc_pool_create(struct device *dev, u32 phys, u32 hw_addr, if (phys) { pool->phys = phys; - pool->iomap = ioremap(phys, size); + pool->iomap = ioremap(phys, size); /* should be memremap? */ pool->hw_addr = hw_addr; } else { - pool->cpumap = dma_alloc_coherent(dev, size, &pool->phys, + pool->cpumap = dma_alloc_coherent(dev, size, &pool->hw_addr, GFP_KERNEL); - pool->iomap = pool->cpumap; - pool->hw_addr = pool->phys; + pool->iomap = (void __iomem __force *)pool->cpumap; + pool->phys = pool->hw_addr; /* assumes no IOMMU, don't use this value */ } if (pool->iomap) diff --git a/drivers/net/ethernet/ti/davinci_mdio.c b/drivers/net/ethernet/ti/davinci_mdio.c index c00084d..cfbe9a5 100644 --- a/drivers/net/ethernet/ti/davinci_mdio.c +++ b/drivers/net/ethernet/ti/davinci_mdio.c @@ -102,6 +102,10 @@ struct davinci_mdio_data { bool skip_scan; }; +#if IS_ENABLED(CONFIG_OF) +static void davinci_mdio_update_dt_from_phymask(u32 phy_mask); +#endif + static void __davinci_mdio_reset(struct davinci_mdio_data *data) { u32 mdio_in, div, mdio_out_khz, access_time; @@ -158,6 +162,12 @@ static int davinci_mdio_reset(struct mii_bus *bus) /* restrict mdio bus to live phys only */ dev_info(data->dev, "detected phy mask %x\n", ~phy_mask); phy_mask = ~phy_mask; + + #if IS_ENABLED(CONFIG_OF) + if (of_machine_is_compatible("ti,am335x-bone")) + davinci_mdio_update_dt_from_phymask(phy_mask); + #endif + } else { /* desperately scan all phys */ dev_warn(data->dev, "no live phy, scanning all\n"); @@ -318,6 +328,93 @@ static int davinci_mdio_probe_dt(struct mdio_platform_data *data, return 0; } +static void davinci_mdio_update_dt_from_phymask(u32 phy_mask) +{ + int i, len, skip; + u32 addr; + __be32 *old_phy_p, *phy_id_p; + struct property *phy_id_property = NULL; + struct device_node *node_p, *slave_p; + + addr = 0; + + for (i = 0; i < PHY_MAX_ADDR; i++) { + if ((phy_mask & (1 << i)) == 0) { + addr = (u32) i; + break; + } + } + + for_each_compatible_node(node_p, NULL, "ti,cpsw") { + for_each_node_by_name(slave_p, "slave") { + +#if IS_ENABLED(CONFIG_OF_OVERLAY) + skip = 1; + // Hack, the overlay fixup "slave" doesn't have phy-mode... + old_phy_p = (__be32 *) of_get_property(slave_p, "phy-mode", &len); + + if (len != (sizeof(__be32 *) * 1)) + { + skip = 0; + } + + if (skip) { +#endif + + old_phy_p = (__be32 *) of_get_property(slave_p, "phy_id", &len); + + if (len != (sizeof(__be32 *) * 2)) + goto err_out; + + if (old_phy_p) { + + phy_id_property = kzalloc(sizeof(*phy_id_property), GFP_KERNEL); + + if (! phy_id_property) + goto err_out; + + phy_id_property->length = len; + phy_id_property->name = kstrdup("phy_id", GFP_KERNEL); + phy_id_property->value = kzalloc(len, GFP_KERNEL); + + if (! phy_id_property->name) + goto err_out; + + if (! phy_id_property->value) + goto err_out; + + memcpy(phy_id_property->value, old_phy_p, len); + + phy_id_p = (__be32 *) phy_id_property->value + 1; + + *phy_id_p = cpu_to_be32(addr); + + of_update_property(slave_p, phy_id_property); + pr_info("davinci_mdio: dt: updated phy_id[%d] from phy_mask[%x]\n", addr, phy_mask); + + ++addr; + } +#if IS_ENABLED(CONFIG_OF_OVERLAY) + } +#endif + } + } + + return; + +err_out: + + if (phy_id_property) { + if (phy_id_property->name) + kfree(phy_id_property->name); + + if (phy_id_property->value) + kfree(phy_id_property->value); + + if (phy_id_property) + kfree(phy_id_property); + } +} #endif static int davinci_mdio_probe(struct platform_device *pdev) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 6fd4e5a..9ad1c2c 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -38,8 +38,13 @@ struct nvmem_device { int users; size_t size; bool read_only; + int flags; + struct bin_attribute eeprom; + struct device *base_dev; }; +#define FLAG_COMPAT BIT(0) + struct nvmem_cell { const char *name; int offset; @@ -56,16 +61,26 @@ static DEFINE_IDA(nvmem_ida); static LIST_HEAD(nvmem_cells); static DEFINE_MUTEX(nvmem_cells_mutex); +#ifdef CONFIG_DEBUG_LOCK_ALLOC +static struct lock_class_key eeprom_lock_key; +#endif + #define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); - struct nvmem_device *nvmem = to_nvmem_device(dev); + struct device *dev; + struct nvmem_device *nvmem; int rc; + if (attr->private) + dev = attr->private; + else + dev = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + /* Stop the user from reading */ if (pos >= nvmem->size) return 0; @@ -87,10 +102,16 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { - struct device *dev = container_of(kobj, struct device, kobj); - struct nvmem_device *nvmem = to_nvmem_device(dev); + struct device *dev; + struct nvmem_device *nvmem; int rc; + if (attr->private) + dev = attr->private; + else + dev = container_of(kobj, struct device, kobj); + nvmem = to_nvmem_device(dev); + /* Stop the user from writing */ if (pos >= nvmem->size) return 0; @@ -155,6 +176,53 @@ static const struct attribute_group *nvmem_ro_dev_groups[] = { NULL, }; +/* default read/write permissions, root only */ +static struct bin_attribute bin_attr_rw_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = S_IWUSR | S_IRUSR, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +static struct bin_attribute *nvmem_bin_rw_root_attributes[] = { + &bin_attr_rw_root_nvmem, + NULL, +}; + +static const struct attribute_group nvmem_bin_rw_root_group = { + .bin_attrs = nvmem_bin_rw_root_attributes, +}; + +static const struct attribute_group *nvmem_rw_root_dev_groups[] = { + &nvmem_bin_rw_root_group, + NULL, +}; + +/* read only permission, root only */ +static struct bin_attribute bin_attr_ro_root_nvmem = { + .attr = { + .name = "nvmem", + .mode = S_IRUSR, + }, + .read = bin_attr_nvmem_read, +}; + +static struct bin_attribute *nvmem_bin_ro_root_attributes[] = { + &bin_attr_ro_root_nvmem, + NULL, +}; + +static const struct attribute_group nvmem_bin_ro_root_group = { + .bin_attrs = nvmem_bin_ro_root_attributes, +}; + +static const struct attribute_group *nvmem_ro_root_dev_groups[] = { + &nvmem_bin_ro_root_group, + NULL, +}; + static void nvmem_release(struct device *dev) { struct nvmem_device *nvmem = to_nvmem_device(dev); @@ -294,6 +362,43 @@ err: return rval; } +/* + * nvmem_setup_compat() - Create an additional binary entry in + * drivers sys directory, to be backwards compatible with the older + * drivers/misc/eeprom drivers. + */ +static int nvmem_setup_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + int rval; + + if (!config->base_dev) + return -EINVAL; + + if (nvmem->read_only) + nvmem->eeprom = bin_attr_ro_root_nvmem; + else + nvmem->eeprom = bin_attr_rw_root_nvmem; + nvmem->eeprom.attr.name = "eeprom"; + nvmem->eeprom.size = nvmem->size; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + nvmem->eeprom.attr.key = &eeprom_lock_key; +#endif + nvmem->eeprom.private = &nvmem->dev; + nvmem->base_dev = config->base_dev; + + rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); + if (rval) { + dev_err(&nvmem->dev, + "Failed to create eeprom binary file %d\n", rval); + return rval; + } + + nvmem->flags |= FLAG_COMPAT; + + return 0; +} + /** * nvmem_register() - Register a nvmem device for given nvmem_config. * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem @@ -347,24 +452,37 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->read_only = of_property_read_bool(np, "read-only") | config->read_only; - nvmem->dev.groups = nvmem->read_only ? nvmem_ro_dev_groups : - nvmem_rw_dev_groups; + if (config->root_only) + nvmem->dev.groups = nvmem->read_only ? + nvmem_ro_root_dev_groups : + nvmem_rw_root_dev_groups; + else + nvmem->dev.groups = nvmem->read_only ? + nvmem_ro_dev_groups : + nvmem_rw_dev_groups; device_initialize(&nvmem->dev); dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); rval = device_add(&nvmem->dev); - if (rval) { - ida_simple_remove(&nvmem_ida, nvmem->id); - kfree(nvmem); - return ERR_PTR(rval); + if (rval) + goto out; + + if (config->compat) { + rval = nvmem_setup_compat(nvmem, config); + if (rval) + goto out; } if (config->cells) nvmem_add_cells(nvmem, config); return nvmem; +out: + ida_simple_remove(&nvmem_ida, nvmem->id); + kfree(nvmem); + return ERR_PTR(rval); } EXPORT_SYMBOL_GPL(nvmem_register); @@ -384,6 +502,9 @@ int nvmem_unregister(struct nvmem_device *nvmem) } mutex_unlock(&nvmem_mutex); + if (nvmem->flags & FLAG_COMPAT) + device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); + nvmem_device_remove_all_cells(nvmem); device_del(&nvmem->dev); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index e2a4841..59efeda 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -112,4 +112,11 @@ config OF_OVERLAY While this option is selected automatically when needed, you can enable it manually to improve device tree unit test coverage. +config OF_CONFIGFS + bool "Device Tree Overlay ConfigFS interface" + select CONFIGFS_FS + depends on OF_OVERLAY + help + Enable a simple user-space driven DT overlay interface. + endif # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 156c072..46c8f57 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -1,4 +1,5 @@ obj-y = base.o device.o platform.o +obj-$(CONFIG_OF_CONFIGFS) += configfs.o obj-$(CONFIG_OF_DYNAMIC) += dynamic.o obj-$(CONFIG_OF_FLATTREE) += fdt.o obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o diff --git a/drivers/of/base.c b/drivers/of/base.c index 3134129..2d0b1de 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "of_private.h" @@ -41,6 +42,16 @@ static const char *of_stdout_options; struct kset *of_kset; +const struct rhashtable_params of_phandle_ht_params = { + .key_offset = offsetof(struct device_node, phandle), /* base offset */ + .key_len = sizeof(phandle), + .head_offset = offsetof(struct device_node, ht_node), + .automatic_shrinking = true, +}; + +struct rhashtable of_phandle_ht; +bool of_phandle_ht_initialized; + /* * Used to protect the of_aliases, to hold off addition of nodes to sysfs. * This mutex must be held whenever modifications are being made to the @@ -160,13 +171,19 @@ int __of_add_property_sysfs(struct device_node *np, struct property *pp) return rc; } -int __of_attach_node_sysfs(struct device_node *np) +int __of_attach_node_post(struct device_node *np) { const char *name; struct kobject *parent; struct property *pp; int rc; + if (of_phandle_ht_available()) { + rc = of_phandle_ht_insert(np); + WARN(rc, "insert to phandle hash fail @%s\n", + of_node_full_name(np)); + } + if (!IS_ENABLED(CONFIG_SYSFS)) return 0; @@ -198,6 +215,14 @@ int __of_attach_node_sysfs(struct device_node *np) void __init of_core_init(void) { struct device_node *np; + int ret; + + ret = rhashtable_init(&of_phandle_ht, &of_phandle_ht_params); + if (ret) { + pr_warn("devicetree: Failed to initialize hashtable\n"); + return; + } + of_phandle_ht_initialized = 1; /* Create the kset, and register existing nodes */ mutex_lock(&of_mutex); @@ -208,12 +233,16 @@ void __init of_core_init(void) return; } for_each_of_allnodes(np) - __of_attach_node_sysfs(np); + __of_attach_node_post(np); mutex_unlock(&of_mutex); /* Symlink in /proc as required by userspace ABI */ if (of_root) proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base"); + + ret = of_overlay_init(); + if (ret != 0) + pr_warn("of_init: of_overlay_init failed!\n"); } static struct property *__of_find_property(const struct device_node *np, @@ -1074,9 +1103,14 @@ struct device_node *of_find_node_by_phandle(phandle handle) return NULL; raw_spin_lock_irqsave(&devtree_lock, flags); - for_each_of_allnodes(np) - if (np->phandle == handle) - break; + /* when we're ready use the hash table */ + if (of_phandle_ht_available() && !in_interrupt()) + np = of_phandle_ht_lookup(handle); + else { /* fallback */ + for_each_of_allnodes(np) + if (np->phandle == handle) + break; + } of_node_get(np); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; diff --git b/drivers/of/configfs.c b/drivers/of/configfs.c new file mode 100644 index 0000000..b68dea8 --- /dev/null +++ b/drivers/of/configfs.c @@ -0,0 +1,311 @@ +/* + * Configfs entries for device-tree + * + * Copyright (C) 2013 - Pantelis Antoniou + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "of_private.h" + +struct cfs_overlay_item { + struct config_item item; + + char path[PATH_MAX]; + + const struct firmware *fw; + struct device_node *overlay; + int ov_id; + + void *dtbo; + int dtbo_size; +}; + +static int create_overlay(struct cfs_overlay_item *overlay, void *blob) +{ + int err; + + /* unflatten the tree */ + of_fdt_unflatten_tree(blob, &overlay->overlay); + if (overlay->overlay == NULL) { + pr_err("%s: failed to unflatten tree\n", __func__); + err = -EINVAL; + goto out_err; + } + pr_debug("%s: unflattened OK\n", __func__); + + /* mark it as detached */ + of_node_set_flag(overlay->overlay, OF_DETACHED); + + /* perform resolution */ + err = of_resolve_phandles(overlay->overlay); + if (err != 0) { + pr_err("%s: Failed to resolve tree\n", __func__); + goto out_err; + } + pr_debug("%s: resolved OK\n", __func__); + + err = of_overlay_create(overlay->overlay); + if (err < 0) { + pr_err("%s: Failed to create overlay (err=%d)\n", + __func__, err); + goto out_err; + } + overlay->ov_id = err; + +out_err: + return err; +} + +static inline struct cfs_overlay_item *to_cfs_overlay_item( + struct config_item *item) +{ + return item ? container_of(item, struct cfs_overlay_item, item) : NULL; +} + +static ssize_t cfs_overlay_item_path_show(struct config_item *item, char *page) +{ + return sprintf(page, "%s\n", to_cfs_overlay_item(item)->path); +} + +static ssize_t cfs_overlay_item_path_store(struct config_item *item, + const char *page, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + const char *p = page; + char *s; + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy to path buffer (and make sure it's always zero terminated */ + count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p); + overlay->path[sizeof(overlay->path) - 1] = '\0'; + + /* strip trailing newlines */ + s = overlay->path + strlen(overlay->path); + while (s > overlay->path && *--s == '\n') + *s = '\0'; + + pr_debug("%s: path is '%s'\n", __func__, overlay->path); + + err = request_firmware(&overlay->fw, overlay->path, NULL); + if (err != 0) + goto out_err; + + err = create_overlay(overlay, (void *)overlay->fw->data); + if (err < 0) + goto out_err; + + return count; + +out_err: + + release_firmware(overlay->fw); + overlay->fw = NULL; + + overlay->path[0] = '\0'; + return err; +} + +static ssize_t cfs_overlay_item_status_show(struct config_item *item, + char *page) +{ + return sprintf(page, "%s\n", to_cfs_overlay_item(item)->ov_id >= 0 ? + "applied" : "unapplied"); +} + +CONFIGFS_ATTR(cfs_overlay_item_, path); +CONFIGFS_ATTR_RO(cfs_overlay_item_, status); + +static struct configfs_attribute *cfs_overlay_attrs[] = { + &cfs_overlay_item_attr_path, + &cfs_overlay_item_attr_status, + NULL, +}; + +ssize_t cfs_overlay_item_dtbo_read(struct config_item *item, void *buf, + size_t max_count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + pr_debug("%s: buf=%p max_count=%u\n", __func__, + buf, max_count); + + if (overlay->dtbo == NULL) + return 0; + + /* copy if buffer provided */ + if (buf != NULL) { + /* the buffer must be large enough */ + if (overlay->dtbo_size > max_count) + return -ENOSPC; + + memcpy(buf, overlay->dtbo, overlay->dtbo_size); + } + + return overlay->dtbo_size; +} + +ssize_t cfs_overlay_item_dtbo_write(struct config_item *item, const void *buf, + size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy the contents */ + overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); + if (overlay->dtbo == NULL) + return -ENOMEM; + + overlay->dtbo_size = count; + + err = create_overlay(overlay, overlay->dtbo); + if (err < 0) + goto out_err; + + return count; + +out_err: + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + + return err; +} + +CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M); + +static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { + &cfs_overlay_item_attr_dtbo, + NULL, +}; + +static void cfs_overlay_release(struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + if (overlay->ov_id >= 0) + of_overlay_destroy(overlay->ov_id); + if (overlay->fw) + release_firmware(overlay->fw); + /* kfree with NULL is safe */ + kfree(overlay->dtbo); + kfree(overlay); +} + +static struct configfs_item_operations cfs_overlay_item_ops = { + .release = cfs_overlay_release, +}; + +static struct config_item_type cfs_overlay_type = { + .ct_item_ops = &cfs_overlay_item_ops, + .ct_attrs = cfs_overlay_attrs, + .ct_bin_attrs = cfs_overlay_bin_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *cfs_overlay_group_make_item( + struct config_group *group, const char *name) +{ + struct cfs_overlay_item *overlay; + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + if (!overlay) + return ERR_PTR(-ENOMEM); + overlay->ov_id = -1; + + config_item_init_type_name(&overlay->item, name, &cfs_overlay_type); + return &overlay->item; +} + +static void cfs_overlay_group_drop_item(struct config_group *group, + struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + config_item_put(&overlay->item); +} + +static struct configfs_group_operations overlays_ops = { + .make_item = cfs_overlay_group_make_item, + .drop_item = cfs_overlay_group_drop_item, +}; + +static struct config_item_type overlays_type = { + .ct_group_ops = &overlays_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_group_operations of_cfs_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type of_cfs_type = { + .ct_group_ops = &of_cfs_ops, + .ct_owner = THIS_MODULE, +}; + +struct config_group of_cfs_overlay_group; + +struct config_group *of_cfs_def_groups[] = { + &of_cfs_overlay_group, + NULL +}; + +static struct configfs_subsystem of_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "device-tree", + .ci_type = &of_cfs_type, + }, + .default_groups = of_cfs_def_groups, + }, + .su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex), +}; + +static int __init of_cfs_init(void) +{ + int ret; + + pr_info("%s\n", __func__); + + config_group_init(&of_cfs_subsys.su_group); + config_group_init_type_name(&of_cfs_overlay_group, "overlays", + &overlays_type); + + ret = configfs_register_subsystem(&of_cfs_subsys); + if (ret != 0) { + pr_err("%s: failed to register subsys\n", __func__); + goto out; + } + pr_info("%s: OK\n", __func__); +out: + return ret; +} +late_initcall(of_cfs_init); diff --git a/drivers/of/dynamic.c b/drivers/of/dynamic.c index 2d72ddc..7c3e17f 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "of_private.h" @@ -41,9 +42,16 @@ void of_node_put(struct device_node *node) } EXPORT_SYMBOL(of_node_put); -void __of_detach_node_sysfs(struct device_node *np) +void __of_detach_node_post(struct device_node *np) { struct property *pp; + int rc; + + if (of_phandle_ht_available()) { + rc = of_phandle_ht_remove(np); + WARN(rc, "remove from phandle hash fail @%s\n", + of_node_full_name(np)); + } if (!IS_ENABLED(CONFIG_SYSFS)) return; @@ -251,7 +259,7 @@ int of_attach_node(struct device_node *np) __of_attach_node(np); raw_spin_unlock_irqrestore(&devtree_lock, flags); - __of_attach_node_sysfs(np); + __of_attach_node_post(np); mutex_unlock(&of_mutex); of_reconfig_notify(OF_RECONFIG_ATTACH_NODE, &rd); @@ -304,7 +312,7 @@ int of_detach_node(struct device_node *np) __of_detach_node(np); raw_spin_unlock_irqrestore(&devtree_lock, flags); - __of_detach_node_sysfs(np); + __of_detach_node_post(np); mutex_unlock(&of_mutex); of_reconfig_notify(OF_RECONFIG_DETACH_NODE, &rd); @@ -394,8 +402,9 @@ struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags) } /** - * __of_node_dup() - Duplicate or create an empty device node dynamically. - * @fmt: Format string (plus vargs) for new full name of the device node + * __of_node_dupv() - Duplicate or create an empty device node dynamically. + * @fmt: Format string for new full name of the device node + * @vargs: va_list containing the arugments for the node full name * * Create an device tree node, either by duplicating an empty node or by allocating * an empty one suitable for further modification. The node data are @@ -403,17 +412,15 @@ struct property *__of_prop_dup(const struct property *prop, gfp_t allocflags) * OF_DETACHED bits set. Returns the newly allocated node or NULL on out of * memory error. */ -struct device_node *__of_node_dup(const struct device_node *np, const char *fmt, ...) +struct device_node *__of_node_dupv(const struct device_node *np, + const char *fmt, va_list vargs) { - va_list vargs; struct device_node *node; node = kzalloc(sizeof(*node), GFP_KERNEL); if (!node) return NULL; - va_start(vargs, fmt); node->full_name = kvasprintf(GFP_KERNEL, fmt, vargs); - va_end(vargs); if (!node->full_name) { kfree(node); return NULL; @@ -445,6 +452,24 @@ struct device_node *__of_node_dup(const struct device_node *np, const char *fmt, return NULL; } +/** + * __of_node_dup() - Duplicate or create an empty device node dynamically. + * @fmt: Format string (plus vargs) for new full name of the device node + * + * See: __of_node_dupv() + */ +struct device_node *__of_node_dup(const struct device_node *np, + const char *fmt, ...) +{ + va_list vargs; + struct device_node *node; + + va_start(vargs, fmt); + node = __of_node_dupv(np, fmt, vargs); + va_end(vargs); + return node; +} + static void __of_changeset_entry_destroy(struct of_changeset_entry *ce) { of_node_put(ce->np); @@ -606,10 +631,10 @@ static int __of_changeset_entry_apply(struct of_changeset_entry *ce) switch (ce->action) { case OF_RECONFIG_ATTACH_NODE: - __of_attach_node_sysfs(ce->np); + __of_attach_node_post(ce->np); break; case OF_RECONFIG_DETACH_NODE: - __of_detach_node_sysfs(ce->np); + __of_detach_node_post(ce->np); break; case OF_RECONFIG_ADD_PROPERTY: /* ignore duplicate names */ @@ -646,6 +671,7 @@ void of_changeset_init(struct of_changeset *ocs) memset(ocs, 0, sizeof(*ocs)); INIT_LIST_HEAD(&ocs->entries); } +EXPORT_SYMBOL_GPL(of_changeset_init); /** * of_changeset_destroy - Destroy a changeset @@ -662,20 +688,9 @@ void of_changeset_destroy(struct of_changeset *ocs) list_for_each_entry_safe_reverse(ce, cen, &ocs->entries, node) __of_changeset_entry_destroy(ce); } +EXPORT_SYMBOL_GPL(of_changeset_destroy); -/** - * of_changeset_apply - Applies a changeset - * - * @ocs: changeset pointer - * - * Applies a changeset to the live tree. - * Any side-effects of live tree state changes are applied here on - * sucess, like creation/destruction of devices and side-effects - * like creation of sysfs properties and directories. - * Returns 0 on success, a negative error value in case of an error. - * On error the partially applied effects are reverted. - */ -int of_changeset_apply(struct of_changeset *ocs) +int __of_changeset_apply(struct of_changeset *ocs) { struct of_changeset_entry *ce; int ret; @@ -704,17 +719,30 @@ int of_changeset_apply(struct of_changeset *ocs) } /** - * of_changeset_revert - Reverts an applied changeset + * of_changeset_apply - Applies a changeset * * @ocs: changeset pointer * - * Reverts a changeset returning the state of the tree to what it - * was before the application. - * Any side-effects like creation/destruction of devices and - * removal of sysfs properties and directories are applied. + * Applies a changeset to the live tree. + * Any side-effects of live tree state changes are applied here on + * success, like creation/destruction of devices and side-effects + * like creation of sysfs properties and directories. * Returns 0 on success, a negative error value in case of an error. + * On error the partially applied effects are reverted. */ -int of_changeset_revert(struct of_changeset *ocs) +int of_changeset_apply(struct of_changeset *ocs) +{ + int ret; + + mutex_lock(&of_mutex); + ret = __of_changeset_apply(ocs); + mutex_unlock(&of_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(of_changeset_apply); + +int __of_changeset_revert(struct of_changeset *ocs) { struct of_changeset_entry *ce; int ret; @@ -742,6 +770,29 @@ int of_changeset_revert(struct of_changeset *ocs) } /** + * of_changeset_revert - Reverts an applied changeset + * + * @ocs: changeset pointer + * + * Reverts a changeset returning the state of the tree to what it + * was before the application. + * Any side-effects like creation/destruction of devices and + * removal of sysfs properties and directories are applied. + * Returns 0 on success, a negative error value in case of an error. + */ +int of_changeset_revert(struct of_changeset *ocs) +{ + int ret; + + mutex_lock(&of_mutex); + ret = __of_changeset_revert(ocs); + mutex_unlock(&of_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(of_changeset_revert); + +/** * of_changeset_action - Perform a changeset action * * @ocs: changeset pointer @@ -779,3 +830,254 @@ int of_changeset_action(struct of_changeset *ocs, unsigned long action, list_add_tail(&ce->node, &ocs->entries); return 0; } +EXPORT_SYMBOL_GPL(of_changeset_action); + +/* changeset helpers */ + +/** + * of_changeset_create_device_node - Create an empty device node + * + * @ocs: changeset pointer + * @parent: parent device node + * @fmt: format string for the node's full_name + * @args: argument list for the format string + * + * Create an empty device node, marking it as detached and allocated. + * + * Returns a device node on success, an error encoded pointer otherwise + */ +struct device_node *of_changeset_create_device_nodev( + struct of_changeset *ocs, struct device_node *parent, + const char *fmt, va_list vargs) +{ + struct device_node *node; + + node = __of_node_dupv(NULL, fmt, vargs); + if (!node) + return ERR_PTR(-ENOMEM); + + node->parent = parent; + return node; +} + +/** + * of_changeset_create_device_node - Create an empty device node + * + * @ocs: changeset pointer + * @parent: parent device node + * @fmt: Format string for the node's full_name + * ... Arguments + * + * Create an empty device node, marking it as detached and allocated. + * + * Returns a device node on success, an error encoded pointer otherwise + */ +struct device_node *of_changeset_create_device_node( + struct of_changeset *ocs, struct device_node *parent, + const char *fmt, ...) +{ + va_list vargs; + struct device_node *node; + + va_start(vargs, fmt); + node = of_changeset_create_device_nodev(ocs, parent, fmt, vargs); + va_end(vargs); + return node; +} + +/** + * of_changeset_add_property_copy - Create a new property copying name & value + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * @value: pointer to the value data + * @length: length of the value in bytes + * + * Adds a property to the changeset by making copies of the name & value + * entries. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_copy(struct of_changeset *ocs, + struct device_node *np, const char *name, const void *value, + int length) +{ + struct property *prop; + char *new_name; + void *new_value; + int ret = -ENOMEM; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) + goto out_no_prop; + + new_name = kstrdup(name, GFP_KERNEL); + if (!new_name) + goto out_no_name; + + /* + * NOTE: There is no check for zero length value. + * In case of a boolean property, this will allocate a value + * of zero bytes. We do this to work around the use + * of of_get_property() calls on boolean values. + */ + new_value = kmemdup(value, length, GFP_KERNEL); + if (!new_value) + goto out_no_value; + + of_property_set_flag(prop, OF_DYNAMIC); + + prop->name = new_name; + prop->value = new_value; + prop->length = length; + + ret = of_changeset_add_property(ocs, np, prop); + if (ret != 0) + goto out_no_add; + + return 0; + +out_no_add: + kfree(prop->value); +out_no_value: + kfree(prop->name); +out_no_name: + kfree(prop); +out_no_prop: + return ret; +} + +/** + * of_changeset_add_property_string - Create a new string property + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * @str: string property + * + * Adds a string property to the changeset by making copies of the name + * and the string value. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_string(struct of_changeset *ocs, + struct device_node *np, const char *name, const char *str) +{ + return of_changeset_add_property_copy(ocs, np, name, str, + strlen(str) + 1); +} + +/** + * of_changeset_add_property_stringf - Create a new formatted string property + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * @fmt: format of string property + * ... arguments of the format string + * + * Adds a string property to the changeset by making copies of the name + * and the formatted value. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_stringf(struct of_changeset *ocs, + struct device_node *np, const char *name, const char *fmt, ...) +{ + va_list vargs; + char *str; + int ret; + + va_start(vargs, fmt); + str = kvasprintf(GFP_KERNEL, fmt, vargs); + va_end(vargs); + + ret = of_changeset_add_property_string(ocs, np, name, str); + + kfree(str); + return ret; +} + +/** + * of_changeset_add_property_string_list - Create a new string list property + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * @strs: pointer to the string list + * @count: string count + * + * Adds a string list property to the changeset. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_string_list(struct of_changeset *ocs, + struct device_node *np, const char *name, const char **strs, + int count) +{ + int total = 0, i, ret; + char *value, *s; + + for (i = 0; i < count; i++) { + /* check if it's NULL */ + if (!strs[i]) + return -EINVAL; + total += strlen(strs[i]) + 1; + } + + value = kmalloc(total, GFP_KERNEL); + if (!value) + return -ENOMEM; + + for (i = 0, s = value; i < count; i++) { + /* no need to check for NULL, check above */ + strcpy(s, strs[i]); + s += strlen(strs[i]) + 1; + } + + ret = of_changeset_add_property_copy(ocs, np, name, value, total); + + kfree(value); + + return ret; +} + +/** + * of_changeset_add_property_u32 - Create a new u32 property + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * @val: value in host endian format + * + * Adds a u32 property to the changeset. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_u32(struct of_changeset *ocs, + struct device_node *np, const char *name, u32 val) +{ + /* in place */ + val = cpu_to_be32(val); + return of_changeset_add_property_copy(ocs, np, name, &val, sizeof(val)); +} + +/** + * of_changeset_add_property_bool - Create a new u32 property + * + * @ocs: changeset pointer + * @np: device node pointer + * @name: name of the property + * + * Adds a bool property to the changeset. Note that there is + * no option to set the value to false, since the property + * existing sets it to true. + * + * Returns zero on success, a negative error value otherwise. + */ +int of_changeset_add_property_bool(struct of_changeset *ocs, + struct device_node *np, const char *name) +{ + return of_changeset_add_property_copy(ocs, np, name, "", 0); +} diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index 655f79d..3349d2a 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -760,6 +760,16 @@ const void * __init of_flat_dt_match_machine(const void *default_match, } #ifdef CONFIG_BLK_DEV_INITRD +#ifndef __early_init_dt_declare_initrd +static void __early_init_dt_declare_initrd(unsigned long start, + unsigned long end) +{ + initrd_start = (unsigned long)__va(start); + initrd_end = (unsigned long)__va(end); + initrd_below_start_ok = 1; +} +#endif + /** * early_init_dt_check_for_initrd - Decode initrd location from flat tree * @node: reference to node containing initrd location ('chosen') @@ -782,9 +792,7 @@ static void __init early_init_dt_check_for_initrd(unsigned long node) return; end = of_read_number(prop, len/4); - initrd_start = (unsigned long)__va(start); - initrd_end = (unsigned long)__va(end); - initrd_below_start_ok = 1; + __early_init_dt_declare_initrd(start, end); pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n", (unsigned long long)start, (unsigned long long)end); @@ -796,14 +804,13 @@ static inline void early_init_dt_check_for_initrd(unsigned long node) #endif /* CONFIG_BLK_DEV_INITRD */ #ifdef CONFIG_SERIAL_EARLYCON -extern struct of_device_id __earlycon_of_table[]; static int __init early_init_dt_scan_chosen_serial(void) { int offset; - const char *p; + const char *p, *q, *options = NULL; int l; - const struct of_device_id *match = __earlycon_of_table; + const struct earlycon_id *match; const void *fdt = initial_boot_params; offset = fdt_path_offset(fdt, "/chosen"); @@ -818,27 +825,26 @@ static int __init early_init_dt_scan_chosen_serial(void) if (!p || !l) return -ENOENT; - /* Remove console options if present */ - l = strchrnul(p, ':') - p; + q = strchrnul(p, ':'); + if (*q != '\0') + options = q + 1; + l = q - p; /* Get the node specified by stdout-path */ offset = fdt_path_offset_namelen(fdt, p, l); - if (offset < 0) - return -ENODEV; - - while (match->compatible[0]) { - u64 addr; + if (offset < 0) { + pr_warn("earlycon: stdout-path %.*s not found\n", l, p); + return 0; + } - if (fdt_node_check_compatible(fdt, offset, match->compatible)) { - match++; + for (match = __earlycon_table; match < __earlycon_table_end; match++) { + if (!match->compatible[0]) continue; - } - addr = fdt_translate_address(fdt, offset); - if (addr == OF_BAD_ADDR) - return -ENXIO; + if (fdt_node_check_compatible(fdt, offset, match->compatible)) + continue; - of_setup_earlycon(addr, match->data); + of_setup_earlycon(match, offset, options); return 0; } return -ENODEV; @@ -976,13 +982,16 @@ int __init early_init_dt_scan_chosen(unsigned long node, const char *uname, } #ifdef CONFIG_HAVE_MEMBLOCK +#ifndef MIN_MEMBLOCK_ADDR +#define MIN_MEMBLOCK_ADDR __pa(PAGE_OFFSET) +#endif #ifndef MAX_MEMBLOCK_ADDR #define MAX_MEMBLOCK_ADDR ((phys_addr_t)~0) #endif void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size) { - const u64 phys_offset = __pa(PAGE_OFFSET); + const u64 phys_offset = MIN_MEMBLOCK_ADDR; if (!PAGE_ALIGNED(base)) { if (size < PAGE_SIZE - (base & ~PAGE_MASK)) { diff --git a/drivers/of/fdt_address.c b/drivers/of/fdt_address.c index 8d3dc6f..dca8f9b 100644 --- a/drivers/of/fdt_address.c +++ b/drivers/of/fdt_address.c @@ -161,7 +161,7 @@ static int __init fdt_translate_one(const void *blob, int parent, * that can be mapped to a cpu physical address). This is not really specified * that way, but this is traditionally the way IBM at least do things */ -u64 __init fdt_translate_address(const void *blob, int node_offset) +static u64 __init fdt_translate_address(const void *blob, int node_offset) { int parent, len; const struct of_bus *bus, *pbus; @@ -239,3 +239,12 @@ u64 __init fdt_translate_address(const void *blob, int node_offset) bail: return result; } + +/** + * of_flat_dt_translate_address - translate DT addr into CPU phys addr + * @node: node in the flat blob + */ +u64 __init of_flat_dt_translate_address(unsigned long node) +{ + return fdt_translate_address(initial_boot_params, node); +} diff --git a/drivers/of/of_private.h b/drivers/of/of_private.h index 46ddbee..a42d22b 100644 --- a/drivers/of/of_private.h +++ b/drivers/of/of_private.h @@ -45,6 +45,8 @@ static inline struct device_node *kobj_to_device_node(struct kobject *kobj) extern int of_property_notify(int action, struct device_node *np, struct property *prop, struct property *old_prop); extern void of_node_release(struct kobject *kobj); +extern int __of_changeset_apply(struct of_changeset *ocs); +extern int __of_changeset_revert(struct of_changeset *ocs); #else /* CONFIG_OF_DYNAMIC */ static inline int of_property_notify(int action, struct device_node *np, struct property *prop, struct property *old_prop) @@ -77,9 +79,9 @@ extern void __of_update_property_sysfs(struct device_node *np, struct property *newprop, struct property *oldprop); extern void __of_attach_node(struct device_node *np); -extern int __of_attach_node_sysfs(struct device_node *np); +extern int __of_attach_node_post(struct device_node *np); extern void __of_detach_node(struct device_node *np); -extern void __of_detach_node_sysfs(struct device_node *np); +extern void __of_detach_node_post(struct device_node *np); extern void __of_sysfs_remove_bin_file(struct device_node *np, struct property *prop); @@ -93,4 +95,44 @@ extern void __of_sysfs_remove_bin_file(struct device_node *np, #define for_each_transaction_entry_reverse(_oft, _te) \ list_for_each_entry_reverse(_te, &(_oft)->te_list, node) +#if defined(CONFIG_OF_OVERLAY) +extern int of_overlay_init(void); +#else +static inline int of_overlay_init(void) +{ + return 0; +} +#endif + +extern const struct rhashtable_params of_phandle_ht_params; +extern struct rhashtable of_phandle_ht; +extern bool of_phandle_ht_initialized; + +static inline bool of_phandle_ht_available(void) +{ + return of_phandle_ht_initialized; +} + +static inline int of_phandle_ht_insert(struct device_node *np) +{ + if (!np || !np->phandle) + return 0; + return rhashtable_insert_fast(&of_phandle_ht, + &np->ht_node, of_phandle_ht_params); +} + +static inline int of_phandle_ht_remove(struct device_node *np) +{ + if (!np || !np->phandle) + return 0; + return rhashtable_remove_fast(&of_phandle_ht, + &np->ht_node, of_phandle_ht_params); +} + +static inline struct device_node *of_phandle_ht_lookup(phandle handle) +{ + return rhashtable_lookup_fast(&of_phandle_ht, + &handle, of_phandle_ht_params); +} + #endif /* _LINUX_OF_PRIVATE_H */ diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index 54e5af9..de5aae1 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -20,11 +20,28 @@ #include #include #include +#include +#include #include "of_private.h" +/* fwd. decl */ +struct of_overlay; +struct of_overlay_info; + +/* an attribute for each fragment */ +struct fragment_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct fragment_attribute *fattr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct fragment_attribute *fattr, + const char *buf, size_t count); + struct of_overlay_info *ovinfo; +}; + /** * struct of_overlay_info - Holds a single overlay info + * @info: info node that contains the target and overlay * @target: target of the overlay operation * @overlay: pointer to the overlay contents node * @@ -32,8 +49,13 @@ * records. */ struct of_overlay_info { + struct of_overlay *ov; + struct device_node *info; struct device_node *target; struct device_node *overlay; + struct attribute_group attr_group; + struct attribute *attrs[2]; + struct fragment_attribute target_attr; }; /** @@ -50,11 +72,26 @@ struct of_overlay { struct list_head node; int count; struct of_overlay_info *ovinfo_tab; + const struct attribute_group **attr_groups; struct of_changeset cset; + struct kobject kobj; + char *indirect_id; + struct device_node *target_root; }; +/* master enable switch; once set to 0 can't be re-enabled */ +static atomic_t ov_enable = ATOMIC_INIT(1); + +static int __init of_overlay_disable_setup(char *str __always_unused) +{ + atomic_set(&ov_enable, 0); + return 1; +} +__setup("of_overlay_disable", of_overlay_disable_setup); + static int of_overlay_apply_one(struct of_overlay *ov, struct device_node *target, const struct device_node *overlay); +static int overlay_removal_is_ok(struct of_overlay *ov); static int of_overlay_apply_single_property(struct of_overlay *ov, struct device_node *target, struct property *prop) @@ -185,35 +222,141 @@ static int of_overlay_apply(struct of_overlay *ov) return 0; } -/* - * Find the target node using a number of different strategies - * in order of preference - * - * "target" property containing the phandle of the target - * "target-path" property containing the path of the target - */ -static struct device_node *find_target_node(struct device_node *info_node) +static struct device_node *find_target_node_direct(struct of_overlay *ov, + struct device_node *info_node) { + struct device_node *target = NULL, *np; const char *path; + char *newpath; u32 val; int ret; /* first try to go by using the target as a phandle */ ret = of_property_read_u32(info_node, "target", &val); - if (ret == 0) - return of_find_node_by_phandle(val); + if (ret == 0) { + target = of_find_node_by_phandle(val); + if (!target) { + pr_err("%s: Could not find target phandle 0x%x\n", + __func__, val); + return NULL; + } + goto check_root; + } - /* now try to locate by path */ + /* failed, try to locate by path */ ret = of_property_read_string(info_node, "target-path", &path); - if (ret == 0) - return of_find_node_by_path(path); + if (ret == 0) { + + if (!ov->target_root) { + target = of_find_node_by_path(path); + if (!target) + pr_err("%s: Could not find target path \"%s\"\n", + __func__, path); + return target; + } + + /* remove preceding '/' from path; relative path */ + if (*path == '/') { + while (*path == '/') + path++; + + newpath = kasprintf(GFP_KERNEL, "%s%s%s", + of_node_full_name(ov->target_root), + *path ? "/" : "", path); + if (!newpath) { + pr_err("%s: Could not allocate \"%s%s%s\"\n", + __func__, + of_node_full_name(ov->target_root), + *path ? "/" : "", path); + return NULL; + } + target = of_find_node_by_path(newpath); + kfree(newpath); - pr_err("%s: Failed to find target for node %p (%s)\n", __func__, - info_node, info_node->name); + return target; + } + /* target is an alias, need to check */ + target = of_find_node_by_path(path); + if (!target) { + pr_err("%s: Could not find alias \"%s\"\n", + __func__, path); + return NULL; + } + goto check_root; + } + + return NULL; + +check_root: + if (!ov->target_root) + return target; + + /* got a target, but we have to check it's under target root */ + for (np = target; np; np = np->parent) { + if (np == ov->target_root) + return target; + } + pr_err("%s: target \"%s\" not under target_root \"%s\"\n", + __func__, of_node_full_name(target), + of_node_full_name(ov->target_root)); + /* target is not under target_root */ + of_node_put(target); return NULL; } +/* + * Find the target node using a number of different strategies + * in order of preference. Respects the indirect id if available. + * + * "target" property containing the phandle of the target + * "target-path" property containing the path of the target + */ +static struct device_node *find_target_node(struct of_overlay *ov, + struct device_node *info_node) +{ + struct device_node *target; + struct device_node *target_indirect; + struct device_node *indirect; + + /* try direct target */ + target = find_target_node_direct(ov, info_node); + if (target) + return target; + + /* try indirect if there */ + if (!ov->indirect_id) + return NULL; + + target_indirect = of_get_child_by_name(info_node, "target-indirect"); + if (!target_indirect) { + pr_err("%s: Failed to find target-indirect node at %s\n", + __func__, + of_node_full_name(info_node)); + return NULL; + } + + indirect = of_get_child_by_name(target_indirect, ov->indirect_id); + of_node_put(target_indirect); + if (!indirect) { + pr_err("%s: Failed to find indirect child node \"%s\" at %s\n", + __func__, ov->indirect_id, + of_node_full_name(info_node)); + return NULL; + } + + target = find_target_node_direct(ov, indirect); + + if (!target) { + pr_err("%s: Failed to find target for \"%s\" at %s\n", + __func__, ov->indirect_id, + of_node_full_name(indirect)); + } + of_node_put(indirect); + + return target; +} + /** * of_fill_overlay_info() - Fill an overlay info structure * @ov Overlay to fill @@ -235,10 +378,12 @@ static int of_fill_overlay_info(struct of_overlay *ov, if (ovinfo->overlay == NULL) goto err_fail; - ovinfo->target = find_target_node(info_node); + ovinfo->target = find_target_node(ov, info_node); if (ovinfo->target == NULL) goto err_fail; + ovinfo->info = of_node_get(info_node); + return 0; err_fail: @@ -249,6 +394,17 @@ err_fail: return -EINVAL; } +static ssize_t target_show(struct kobject *kobj, + struct fragment_attribute *fattr, char *buf) +{ + struct of_overlay_info *ovinfo = fattr->ovinfo; + + return snprintf(buf, PAGE_SIZE, "%s\n", + of_node_full_name(ovinfo->target)); +} + +static const struct fragment_attribute target_template_attr = __ATTR_RO(target); + /** * of_build_overlay_info() - Build an overlay info array * @ov Overlay to build @@ -266,7 +422,7 @@ static int of_build_overlay_info(struct of_overlay *ov, { struct device_node *node; struct of_overlay_info *ovinfo; - int cnt, err; + int i, cnt, err; /* worst case; every child is a node */ cnt = 0; @@ -287,14 +443,45 @@ static int of_build_overlay_info(struct of_overlay *ov, /* if nothing filled, return error */ if (cnt == 0) { - kfree(ovinfo); - return -ENODEV; + err = -ENODEV; + goto err_free_ovinfo; } ov->count = cnt; ov->ovinfo_tab = ovinfo; + ov->attr_groups = kcalloc(cnt + 1, + sizeof(struct attribute_group *), GFP_KERNEL); + if (ov->attr_groups == NULL) { + err = -ENOMEM; + goto err_free_ovinfo; + } + + for (i = 0; i < cnt; i++) { + ovinfo = &ov->ovinfo_tab[i]; + + ov->attr_groups[i] = &ovinfo->attr_group; + + ovinfo->target_attr = target_template_attr; + /* make lockdep happy */ + sysfs_attr_init(&ovinfo->target_attr.attr); + ovinfo->target_attr.ovinfo = ovinfo; + + ovinfo->attrs[0] = &ovinfo->target_attr.attr; + ovinfo->attrs[1] = NULL; + + /* NOTE: direct reference to the full_name */ + ovinfo->attr_group.name = kbasename(ovinfo->info->full_name); + ovinfo->attr_group.attrs = ovinfo->attrs; + + } + ov->attr_groups[i] = NULL; + return 0; + +err_free_ovinfo: + kfree(ovinfo); + return err; } /** @@ -311,12 +498,16 @@ static int of_free_overlay_info(struct of_overlay *ov) struct of_overlay_info *ovinfo; int i; + /* free attribute groups space */ + kfree(ov->attr_groups); + /* do it in reverse */ for (i = ov->count - 1; i >= 0; i--) { ovinfo = &ov->ovinfo_tab[i]; of_node_put(ovinfo->target); of_node_put(ovinfo->overlay); + of_node_put(ovinfo->info); } kfree(ov->ovinfo_tab); @@ -326,31 +517,104 @@ static int of_free_overlay_info(struct of_overlay *ov) static LIST_HEAD(ov_list); static DEFINE_IDR(ov_idr); -/** - * of_overlay_create() - Create and apply an overlay - * @tree: Device node containing all the overlays - * - * Creates and applies an overlay while also keeping track - * of the overlay in a list. This list can be used to prevent - * illegal overlay removals. - * - * Returns the id of the created overlay, or a negative error number - */ -int of_overlay_create(struct device_node *tree) +static inline struct of_overlay *kobj_to_overlay(struct kobject *kobj) +{ + return container_of(kobj, struct of_overlay, kobj); +} + +void of_overlay_release(struct kobject *kobj) +{ + struct of_overlay *ov = kobj_to_overlay(kobj); + + of_node_put(ov->target_root); + kfree(ov->indirect_id); + kfree(ov); +} + +static ssize_t enable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&ov_enable)); +} + +static ssize_t enable_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret; + bool new_enable; + + ret = strtobool(buf, &new_enable); + if (ret != 0) + return ret; + /* if we've disabled it, no going back */ + if (atomic_read(&ov_enable) == 0) + return -EPERM; + atomic_set(&ov_enable, (int)new_enable); + return count; +} + +static struct kobj_attribute enable_attr = __ATTR_RW(enable); + +static const struct attribute *overlay_global_attrs[] = { + &enable_attr.attr, + NULL +}; + +static ssize_t can_remove_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct of_overlay *ov = kobj_to_overlay(kobj); + + return snprintf(buf, PAGE_SIZE, "%d\n", overlay_removal_is_ok(ov)); +} + +static struct kobj_attribute can_remove_attr = __ATTR_RO(can_remove); + +static struct attribute *overlay_attrs[] = { + &can_remove_attr.attr, + NULL +}; + +static struct kobj_type of_overlay_ktype = { + .release = of_overlay_release, + .sysfs_ops = &kobj_sysfs_ops, /* default kobj sysfs ops */ + .default_attrs = overlay_attrs, +}; + +static struct kset *ov_kset; + +static int __of_overlay_create(struct device_node *tree, + const char *indirect_id, struct device_node *target_root) { struct of_overlay *ov; int err, id; + /* administratively disabled */ + if (!atomic_read(&ov_enable)) + return -EPERM; + /* allocate the overlay structure */ ov = kzalloc(sizeof(*ov), GFP_KERNEL); if (ov == NULL) return -ENOMEM; ov->id = -1; + if (indirect_id) { + ov->indirect_id = kstrdup(indirect_id, GFP_KERNEL); + if (!ov->indirect_id) { + err = -ENOMEM; + goto err_no_mem; + } + } + ov->target_root = of_node_get(target_root); + INIT_LIST_HEAD(&ov->node); of_changeset_init(&ov->cset); + /* initialize kobject */ + kobject_init(&ov->kobj, &of_overlay_ktype); + mutex_lock(&of_mutex); id = idr_alloc(&ov_idr, ov, 0, 0, GFP_KERNEL); @@ -379,20 +643,38 @@ int of_overlay_create(struct device_node *tree) } /* apply the changeset */ - err = of_changeset_apply(&ov->cset); + err = __of_changeset_apply(&ov->cset); if (err) { - pr_err("%s: of_changeset_apply() failed for tree@%s\n", + pr_err("%s: __of_changeset_apply() failed for tree@%s\n", __func__, tree->full_name); goto err_revert_overlay; } + ov->kobj.kset = ov_kset; + err = kobject_add(&ov->kobj, NULL, "%d", id); + if (err != 0) { + pr_err("%s: kobject_add() failed for tree@%s\n", + __func__, tree->full_name); + goto err_cancel_overlay; + } + + err = sysfs_create_groups(&ov->kobj, ov->attr_groups); + if (err != 0) { + pr_err("%s: sysfs_create_groups() failed for tree@%s\n", + __func__, tree->full_name); + goto err_remove_kobj; + } + /* add to the tail of the overlay list */ list_add_tail(&ov->node, &ov_list); mutex_unlock(&of_mutex); return id; - +err_remove_kobj: + kobject_put(&ov->kobj); +err_cancel_overlay: + __of_changeset_revert(&ov->cset); err_revert_overlay: err_abort_trans: of_free_overlay_info(ov); @@ -400,13 +682,68 @@ err_free_idr: idr_remove(&ov_idr, ov->id); err_destroy_trans: of_changeset_destroy(&ov->cset); +err_no_mem: + of_node_put(ov->target_root); + kfree(ov->indirect_id); kfree(ov); mutex_unlock(&of_mutex); return err; } + +/** + * of_overlay_create() - Create and apply an overlay + * @tree: Device node containing all the overlays + * + * Creates and applies an overlay while also keeping track + * of the overlay in a list. This list can be used to prevent + * illegal overlay removals. + * + * Returns the id of the created overlay, or an negative error number + */ +int of_overlay_create(struct device_node *tree) +{ + return __of_overlay_create(tree, NULL, NULL); +} EXPORT_SYMBOL_GPL(of_overlay_create); +/** + * of_overlay_create_indirect() - Create and apply an overlay + * @tree: Device node containing all the overlays + * @id: Indirect property phandle + * + * Creates and applies an overlay while also keeping track + * of the overlay in a list. This list can be used to prevent + * illegal overlay removals. + * + * Returns the id of the created overlay, or an negative error number + */ +int of_overlay_create_indirect(struct device_node *tree, const char *id) +{ + return __of_overlay_create(tree, id, NULL); +} +EXPORT_SYMBOL_GPL(of_overlay_create_indirect); + +/** + * of_overlay_create_target_root() - Create and apply an overlay + * under which will be limited to target_root + * @tree: Device node containing all the overlays + * @target_root: Target root for the overlay. + * + * Creates and applies an overlay while also keeping track + * of the overlay in a list. This list can be used to prevent + * illegal overlay removals. The overlay is only allowed to + * target nodes under the target_root node. + * + * Returns the id of the created overlay, or an negative error number + */ +int of_overlay_create_target_root(struct device_node *tree, + struct device_node *target_root) +{ + return __of_overlay_create(tree, NULL, target_root); +} +EXPORT_SYMBOL_GPL(of_overlay_create_target_root); + /* check whether the given node, lies under the given tree */ static int overlay_subtree_check(struct device_node *tree, struct device_node *dn) @@ -511,11 +848,13 @@ int of_overlay_destroy(int id) list_del(&ov->node); - of_changeset_revert(&ov->cset); + sysfs_remove_groups(&ov->kobj, ov->attr_groups); + __of_changeset_revert(&ov->cset); of_free_overlay_info(ov); idr_remove(&ov_idr, id); of_changeset_destroy(&ov->cset); - kfree(ov); + + kobject_put(&ov->kobj); err = 0; @@ -542,10 +881,10 @@ int of_overlay_destroy_all(void) /* the tail of list is guaranteed to be safe to remove */ list_for_each_entry_safe_reverse(ov, ovn, &ov_list, node) { list_del(&ov->node); - of_changeset_revert(&ov->cset); + __of_changeset_revert(&ov->cset); of_free_overlay_info(ov); idr_remove(&ov_idr, ov->id); - kfree(ov); + kobject_put(&ov->kobj); } mutex_unlock(&of_mutex); @@ -553,3 +892,18 @@ int of_overlay_destroy_all(void) return 0; } EXPORT_SYMBOL_GPL(of_overlay_destroy_all); + +/* called from of_init() */ +int of_overlay_init(void) +{ + int rc; + + ov_kset = kset_create_and_add("overlays", NULL, &of_kset->kobj); + if (!ov_kset) + return -ENOMEM; + + rc = sysfs_create_files(&ov_kset->kobj, overlay_global_attrs); + WARN(rc, "%s: error adding global attributes\n", __func__); + + return rc; +} diff --git a/drivers/of/unittest-data/testcases.dts b/drivers/of/unittest-data/testcases.dts index 12f7c3d..8052b96 100644 --- a/drivers/of/unittest-data/testcases.dts +++ b/drivers/of/unittest-data/testcases.dts @@ -75,5 +75,19 @@ target = <0x00000000>; }; }; + overlay16 { + fragment@0 { + target-indirect { + unittest16 { + target = <0x00000000>; + }; + }; + }; + }; + overlay18 { + fragment@0 { + target = <0x00000000>; + }; + }; }; }; }; diff --git a/drivers/of/unittest-data/tests-overlay.dtsi b/drivers/of/unittest-data/tests-overlay.dtsi index 02ba56c..e10ff5a 100644 --- a/drivers/of/unittest-data/tests-overlay.dtsi +++ b/drivers/of/unittest-data/tests-overlay.dtsi @@ -110,6 +110,30 @@ }; }; }; + + unittest16: test-unittest16 { + compatible = "unittest"; + status = "disabled"; + reg = <16>; + }; + + unittest17: test-unittest17 { + compatible = "unittest"; + status = "disabled"; + reg = <17>; + }; + + unittest18: test-unittest18 { + compatible = "unittest"; + status = "disabled"; + reg = <18>; + }; + + unittest19: test-unittest19 { + compatible = "unittest"; + status = "disabled"; + reg = <19>; + }; }; }; @@ -325,5 +349,48 @@ }; }; + /* test enable using indirect functionality */ + overlay16 { + fragment@0 { + target-indirect { + unittest16 { + target = <&unittest16>; + }; + }; + __overlay__ { + status = "okay"; + }; + }; + }; + + /* test enable using target root (relative path) */ + overlay17 { + fragment@0 { + target-path = "/"; + __overlay__ { + status = "okay"; + }; + }; + }; + + /* test enable using target phandle */ + overlay18 { + fragment@0 { + target = <&unittest18>; + __overlay__ { + status = "okay"; + }; + }; + }; + + /* test trying to enable out of root (should fail) */ + overlay19 { + fragment@0 { + target = <&unittest19>; + __overlay__ { + status = "okay"; + }; + }; + }; }; }; diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c index e16ea57..b3f27b6 100644 --- a/drivers/of/unittest.c +++ b/drivers/of/unittest.c @@ -530,23 +530,58 @@ static void __init of_unittest_changeset(void) unittest(!of_changeset_add_property(&chgset, parent, ppadd), "fail add prop\n"); unittest(!of_changeset_update_property(&chgset, parent, ppupdate), "fail update prop\n"); unittest(!of_changeset_remove_property(&chgset, parent, ppremove), "fail remove prop\n"); - mutex_lock(&of_mutex); unittest(!of_changeset_apply(&chgset), "apply failed\n"); - mutex_unlock(&of_mutex); /* Make sure node names are constructed correctly */ unittest((np = of_find_node_by_path("/testcase-data/changeset/n2/n21")), "'%s' not added\n", n21->full_name); of_node_put(np); - mutex_lock(&of_mutex); unittest(!of_changeset_revert(&chgset), "revert failed\n"); - mutex_unlock(&of_mutex); of_changeset_destroy(&chgset); #endif } +static void __init of_unittest_changeset_helper(void) +{ +#ifdef CONFIG_OF_DYNAMIC + struct device_node *n1, *n2, *n21, *parent, *np; + struct of_changeset chgset; + + of_changeset_init(&chgset); + + parent = of_find_node_by_path("/testcase-data/changeset"); + + unittest(parent, "testcase setup failure\n"); + n1 = of_changeset_create_device_node(&chgset, + parent, "/testcase-data/changeset/n1"); + unittest(n1, "testcase setup failure\n"); + n2 = of_changeset_create_device_node(&chgset, + parent, "/testcase-data/changeset/n2"); + unittest(n2, "testcase setup failure\n"); + n21 = of_changeset_create_device_node(&chgset, n2, "%s/%s", + "/testcase-data/changeset/n2", "n21"); + + of_changeset_init(&chgset); + unittest(!of_changeset_add_property_string(&chgset, parent, + "prop-add", "foo"), "fail add prop\n"); + unittest(!__of_changeset_apply(&chgset), "apply failed\n"); + + /* Make sure node names are constructed correctly */ + unittest((np = of_find_node_by_path("/testcase-data/changeset/n2/n21")), + "'%s' not added\n", n21->full_name); + of_node_put(np); + + unittest(!__of_changeset_revert(&chgset), "revert failed\n"); + + of_changeset_destroy(&chgset); + + of_node_put(parent); +#endif +} + + static void __init of_unittest_parse_interrupts(void) { struct device_node *np; @@ -868,7 +903,7 @@ static int attach_node_and_children(struct device_node *np) of_node_clear_flag(np, OF_DETACHED); raw_spin_unlock_irqrestore(&devtree_lock, flags); - __of_attach_node_sysfs(np); + __of_attach_node_post(np); mutex_unlock(&of_mutex); while (child) { @@ -926,7 +961,7 @@ static int __init unittest_data_add(void) if (!of_root) { of_root = unittest_data_node; for_each_of_allnodes(np) - __of_attach_node_sysfs(np); + __of_attach_node_post(np); of_aliases = of_find_node_by_path("/aliases"); of_chosen = of_find_node_by_path("/chosen"); return 0; @@ -1853,6 +1888,272 @@ static inline void of_unittest_overlay_i2c_15(void) { } #endif +static void of_unittest_overlay_16(void) +{ + int ret; + int overlay_nr = 16; + int unittest_nr = 16; + enum overlay_type ovtype = PDEV_OVERLAY; + int before = 0; + int after = 1; + struct device_node *np = NULL; + int id = -1; + + /* unittest device must not be in before state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != before) { + unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !before ? "enabled" : "disabled"); + return; + } + + np = of_find_node_by_path(overlay_path(overlay_nr)); + if (np == NULL) { + unittest(0, "could not find overlay node @\"%s\"\n", + overlay_path(overlay_nr)); + ret = -EINVAL; + goto out; + } + + ret = of_overlay_create_indirect(np, "unittest16"); + if (ret < 0) { + unittest(0, "could not create overlay from \"%s\"\n", + overlay_path(overlay_nr)); + goto out; + } + id = ret; + of_unittest_track_overlay(id); + + ret = 0; + +out: + of_node_put(np); + + if (ret) + return; + + /* unittest device must be to set to after state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != after) { + unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !after ? "enabled" : "disabled"); + return; + } + + unittest(1, "overlay test %d passed\n", 16); +} + +static void of_unittest_overlay_17(void) +{ + int ret; + int overlay_nr = 17; + int unittest_nr = 17; + enum overlay_type ovtype = PDEV_OVERLAY; + int before = 0; + int after = 1; + const char *root_path; + struct device_node *np = NULL, *target_root = NULL; + int id = -1; + + /* unittest device must not be in before state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != before) { + unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !before ? "enabled" : "disabled"); + return; + } + + np = of_find_node_by_path(overlay_path(overlay_nr)); + if (np == NULL) { + unittest(0, "could not find overlay node @\"%s\"\n", + overlay_path(overlay_nr)); + ret = -EINVAL; + goto out; + } + + root_path = "/testcase-data/overlay-node/test-bus/test-unittest17"; + target_root = of_find_node_by_path(root_path); + if (!target_root) { + unittest(0, "could not find target_root node @\"%s\"\n", + root_path); + ret = -EINVAL; + goto out; + } + + ret = of_overlay_create_target_root(np, target_root); + of_node_put(target_root); + + if (ret < 0) { + unittest(0, "could not create overlay from \"%s\"\n", + overlay_path(overlay_nr)); + goto out; + } + id = ret; + of_unittest_track_overlay(id); + + ret = 0; + +out: + of_node_put(np); + + if (ret) + return; + + /* unittest device must be to set to after state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != after) { + unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !after ? "enabled" : "disabled"); + return; + } + + unittest(1, "overlay test %d passed\n", 17); +} + +static void of_unittest_overlay_18(void) +{ + int ret; + int overlay_nr = 18; + int unittest_nr = 18; + enum overlay_type ovtype = PDEV_OVERLAY; + int before = 0; + int after = 1; + const char *root_path; + struct device_node *np = NULL, *target_root = NULL; + int id = -1; + + /* unittest device must not be in before state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != before) { + unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !before ? "enabled" : "disabled"); + return; + } + + np = of_find_node_by_path(overlay_path(overlay_nr)); + if (np == NULL) { + unittest(0, "could not find overlay node @\"%s\"\n", + overlay_path(overlay_nr)); + ret = -EINVAL; + goto out; + } + + root_path = "/testcase-data/overlay-node/test-bus/test-unittest18"; + target_root = of_find_node_by_path(root_path); + if (!target_root) { + unittest(0, "could not find target_root node @\"%s\"\n", + root_path); + ret = -EINVAL; + goto out; + } + + ret = of_overlay_create_target_root(np, target_root); + of_node_put(target_root); + + if (ret < 0) { + unittest(0, "could not create overlay from \"%s\"\n", + overlay_path(overlay_nr)); + goto out; + } + id = ret; + of_unittest_track_overlay(id); + + ret = 0; + +out: + of_node_put(np); + + if (ret) + return; + + /* unittest device must be to set to after state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != after) { + unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !after ? "enabled" : "disabled"); + return; + } + + unittest(1, "overlay test %d passed\n", 18); +} + +static void of_unittest_overlay_19(void) +{ + int ret; + int overlay_nr = 19; + int unittest_nr = 19; + enum overlay_type ovtype = PDEV_OVERLAY; + int before = 0; + int after = 0; + const char *root_path; + struct device_node *np = NULL, *target_root = NULL; + int id = -1; + + /* unittest device must not be in before state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != before) { + unittest(0, "overlay @\"%s\" with device @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !before ? "enabled" : "disabled"); + return; + } + + np = of_find_node_by_path(overlay_path(overlay_nr)); + if (np == NULL) { + unittest(0, "could not find overlay node @\"%s\"\n", + overlay_path(overlay_nr)); + ret = -EINVAL; + goto out; + } + + root_path = "/testcase-data/overlay-node/test-bus/test-unittest19"; + target_root = of_find_node_by_path(root_path); + if (!target_root) { + unittest(0, "could not find target_root node @\"%s\"\n", + root_path); + ret = -EINVAL; + goto out; + } + + ret = of_overlay_create_target_root(np, target_root); + of_node_put(target_root); + + if (ret >= 0) { + unittest(0, "created overlay from \"%s\" while we shouldn't\n", + overlay_path(overlay_nr)); + id = ret; + of_unittest_track_overlay(id); + ret = -EINVAL; + goto out; + } + + ret = 0; + +out: + of_node_put(np); + + if (ret) + return; + + /* unittest device must be to set to after state */ + if (of_unittest_device_exists(unittest_nr, ovtype) != after) { + unittest(0, "overlay @\"%s\" failed to create @\"%s\" %s\n", + overlay_path(overlay_nr), + unittest_path(unittest_nr, ovtype), + !after ? "enabled" : "disabled"); + return; + } + + unittest(1, "overlay test %d passed\n", 16); +} + + static void __init of_unittest_overlay(void) { struct device_node *bus_np = NULL; @@ -1904,6 +2205,12 @@ static void __init of_unittest_overlay(void) of_unittest_overlay_10(); of_unittest_overlay_11(); + of_unittest_overlay_16(); + + of_unittest_overlay_17(); + of_unittest_overlay_18(); + of_unittest_overlay_19(); + #if IS_BUILTIN(CONFIG_I2C) if (unittest(of_unittest_overlay_i2c_init() == 0, "i2c init failed\n")) goto out; @@ -1954,6 +2261,7 @@ static int __init of_unittest(void) of_unittest_property_string(); of_unittest_property_copy(); of_unittest_changeset(); + of_unittest_changeset_helper(); of_unittest_parse_interrupts(); of_unittest_parse_interrupts_extended(); of_unittest_match_node(); diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 312c78b..458e099 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig @@ -199,6 +199,18 @@ config PINCTRL_TEGRA_XUSB select PINCONF select PINMUX +config PINCTRL_TI_IODELAY + bool "TI IODelay Module pinconf driver" + depends on OF + select PINCONF + select GENERIC_PINCONF + select REGMAP_MMIO + help + Say Y here to support Texas Instruments' IODelay pinconf driver. + IODelay module is used for the DRA7 SoC family. This driver is in + addition to PINCTRL_SINGLE which controls the mux. + + config PINCTRL_TZ1090 bool "Toumaz Xenif TZ1090 pin control driver" depends on SOC_TZ1090 diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 738cb49..4dde911 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_PINCTRL_TEGRA114) += pinctrl-tegra114.o obj-$(CONFIG_PINCTRL_TEGRA124) += pinctrl-tegra124.o obj-$(CONFIG_PINCTRL_TEGRA210) += pinctrl-tegra210.o obj-$(CONFIG_PINCTRL_TEGRA_XUSB) += pinctrl-tegra-xusb.o +obj-$(CONFIG_PINCTRL_TI_IODELAY)+= pinctrl-ti-iodelay.o obj-$(CONFIG_PINCTRL_TZ1090) += pinctrl-tz1090.o obj-$(CONFIG_PINCTRL_TZ1090_PDC) += pinctrl-tz1090-pdc.o obj-$(CONFIG_PINCTRL_U300) += pinctrl-u300.o diff --git b/drivers/pinctrl/pinctrl-ti-iodelay.c b/drivers/pinctrl/pinctrl-ti-iodelay.c new file mode 100644 index 0000000..8d33414 --- /dev/null +++ b/drivers/pinctrl/pinctrl-ti-iodelay.c @@ -0,0 +1,968 @@ +/* + * Support for configuration of IO Delay module found on Texas Instruments SoCs + * such as DRA7 + * + * Copyright (C) 2015 Texas Instruments, Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IODELAY_REG_NAME_LEN ((sizeof(u32) * 2) + 3) +#define DRIVER_NAME "ti-io-delay" +/* Should I change this? Abuse? */ +#define IODELAY_MUX_PINS_NAME "pinctrl-single,pins" + +/* Device tree match, populated later */ +static const struct of_device_id ti_iodelay_of_match[]; + +/** + * struct ti_iodelay_conf_vals - Description of each configuration parameters. + * @offset: Configuration register offset + * @a_delay: Agnostic Delay (in ps) + * @g_delay: Gnostic Delay (in ps) + */ +struct ti_iodelay_conf_vals { + u16 offset; + u16 a_delay; + u16 g_delay; +}; + +/** + * struct ti_iodelay_reg_data - Describes the registers for the IOdelay instance + * @signature_mask: Conf reg- mask for the signature bits + * @signature_value: Conf reg- signature value to be written (see TRM) + * @lock_mask: Conf reg- mask for the lock bits + * @lock_val: Conf reg- lock value for the lock bits (see TRM) + * @unlock_val: Conf reg- unlock value for the lock bits (see TRM) + * @binary_data_coarse_mask: Conf reg- coarse mask (see TRM) + * @binary_data_fine_mask: Conf reg- fine mask (see TRM) + * @reg_refclk_offset: Refclk register offset + * @refclk_period_mask: Refclk mask + * @reg_coarse_offset: Coarse register configuration offset + * @coarse_delay_count_mask: Coarse delay count mask + * @coarse_ref_count_mask: Coarse ref count mask + * @reg_fine_offset: Fine register configuration offset + * @fine_delay_count_mask: Fine delay count mask + * @fine_ref_count_mask: Fine ref count mask + * @reg_global_lock_offset: Global(for the IOdelay module) lock register offset + * @global_lock_mask: Lock mask + * @global_unlock_val: unlock value + * @global_lock_val: lock value + * @reg_start_offset: Where does the configuration registers start? + * @regmap_config: Regmap configuration for the IODelay region + */ +struct ti_iodelay_reg_data { + u32 signature_mask; + u32 signature_value; + u32 lock_mask; + u32 lock_val; + u32 unlock_val; + u32 binary_data_coarse_mask; + u32 binary_data_fine_mask; + + u32 reg_refclk_offset; + u32 refclk_period_mask; + + u32 reg_coarse_offset; + u32 coarse_delay_count_mask; + u32 coarse_ref_count_mask; + + u32 reg_fine_offset; + u32 fine_delay_count_mask; + u32 fine_ref_count_mask; + + u32 reg_global_lock_offset; + u32 global_lock_mask; + u32 global_unlock_val; + u32 global_lock_val; + + u32 reg_start_offset; + + struct regmap_config *regmap_config; +}; + +/** + * struct ti_iodelay_reg_values - Computed io_reg configuration values (see TRM) + * @coarse_ref_count: Coarse reference count + * @coarse_delay_count: Coarse delay count + * @fine_ref_count: Fine reference count + * @fine_delay_count: Fine Delay count + * @ref_clk_period: Reference Clock period + * @cdpe: Coarse delay parameter + * @fdpe: Fine delay parameter + */ +struct ti_iodelay_reg_values { + u16 coarse_ref_count; + u16 coarse_delay_count; + + u16 fine_ref_count; + u16 fine_delay_count; + + u16 ref_clk_period; + + u32 cdpe; + u32 fdpe; +}; + +/** + * struct ti_iodelay_pin_name - name of the pins + * @name: name + */ +struct ti_iodelay_pin_name { + char name[IODELAY_REG_NAME_LEN]; +}; + +/** + * struct ti_iodelay_pingroup - Structure that describes one group + * @np: Node pointer (device tree) + * @name: Name of the group + * @map: pinctrl map allocated for the group + * @vals: configuration values allocated for the group (from dt) + * @nvals: number of configuration values allocated + * @config: pinconf "Config" - currently a dummy value + * @node: list node to next group + */ +struct ti_iodelay_pingroup { + struct device_node *np; + const char *name; + struct pinctrl_map *map; + struct ti_iodelay_conf_vals *vals; + int nvals; + unsigned long config; + struct list_head node; +}; + +/** + * struct ti_iodelay_device - Represents information for a IOdelay instance + * @dev: device pointer + * @reg_base: Remapped virtual address + * @regmap: Regmap for this IOdelay instance + * @pctl: Pinctrl device + * @desc: pinctrl descriptor for pctl + * @pa: pinctrl pin wise description + * @names: names of the pins + * @groups: list of pinconf groups for iodelay instance + * @ngroups: number of groups in the list + * @mutex: mutex to protect group list modification + * @reg_data: Register definition data for the IODelay instance + * @reg_init_conf_values: Initial configuration values. + */ +struct ti_iodelay_device { + struct device *dev; + void __iomem *reg_base; + struct regmap *regmap; + + struct pinctrl_dev *pctl; + struct pinctrl_desc desc; + struct pinctrl_pin_desc *pa; + struct ti_iodelay_pin_name *names; + + struct list_head groups; + int ngroups; + struct mutex mutex; /* list protection */ + + const struct ti_iodelay_reg_data *reg_data; + struct ti_iodelay_reg_values reg_init_conf_values; +}; + +/*--- IOdelay configuration stuff ----*/ + +/** + * ti_iodelay_extract() - extract bits for a field + * @val: register value + * @mask: Mask + * + * Return: extracted value which is appropriately shifted + */ +static inline u32 ti_iodelay_extract(u32 val, u32 mask) +{ + return (val & mask) >> __ffs(mask); +} + +/** + * ti_iodelay_compute_dpe() - Compute equation for delay parameter + * @period: Period to use + * @ref: Reference Count + * @delay: Delay count + * @delay_m: Delay multiplier + * + * Return: Computed delay parameter + */ +static inline u32 ti_iodelay_compute_dpe(u16 period, u16 ref, u16 delay, + u16 delay_m) +{ + u64 m, d; + + /* Handle overflow conditions */ + m = 10 * (u64)period * (u64)ref; + d = 2 * (u64)delay * (u64)delay_m; + + /* Truncate result back to 32 bits */ + return div64_u64(m, d); +} + +/** + * ti_iodelay_pinconf_set() - Configure the pin configuration + * @iod: IODelay device + * @val: Configuration value + * + * Update the configuration register as per TRM and lockup once done. + * *IMPORTANT NOTE* SoC TRM does recommend doing iodelay programmation only + * while in Isolation. But, then, isolation also implies that every pin + * on the SoC(including DDR) will be isolated out. The only benefit being + * a glitchless configuration, However, the intent of this driver is purely + * to support a "glitchy" configuration where applicable. + * + * Return: 0 in case of success, else appropriate error value + */ +static int ti_iodelay_pinconf_set(struct ti_iodelay_device *iod, + struct ti_iodelay_conf_vals *val) +{ + const struct ti_iodelay_reg_data *reg = iod->reg_data; + struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values; + struct device *dev = iod->dev; + u32 g_delay_coarse, g_delay_fine; + u32 a_delay_coarse, a_delay_fine; + u32 c_elements, f_elements; + u32 total_delay; + u32 reg_mask, reg_val, tmp_val; + int r; + + /* NOTE: Truncation is expected in all division below */ + g_delay_coarse = val->g_delay / 920; + g_delay_fine = ((val->g_delay % 920) * 10) / 60; + + a_delay_coarse = val->a_delay / ival->cdpe; + a_delay_fine = ((val->a_delay % ival->cdpe) * 10) / ival->fdpe; + + c_elements = g_delay_coarse + a_delay_coarse; + f_elements = (g_delay_fine + a_delay_fine) / 10; + + if (f_elements > 22) { + total_delay = c_elements * ival->cdpe + f_elements * ival->fdpe; + c_elements = total_delay / ival->cdpe; + f_elements = (total_delay % ival->cdpe) / ival->fdpe; + } + + reg_mask = reg->signature_mask; + reg_val = reg->signature_value << __ffs(reg->signature_mask); + + reg_mask |= reg->binary_data_coarse_mask; + tmp_val = c_elements << __ffs(reg->binary_data_coarse_mask); + if (tmp_val & ~reg->binary_data_coarse_mask) { + dev_err(dev, "Masking overflow of coarse elements %08x\n", + tmp_val); + tmp_val &= reg->binary_data_coarse_mask; + } + reg_val |= tmp_val; + + reg_mask |= reg->binary_data_fine_mask; + tmp_val = f_elements << __ffs(reg->binary_data_fine_mask); + if (tmp_val & ~reg->binary_data_fine_mask) { + dev_err(dev, "Masking overflow of fine elements %08x\n", + tmp_val); + tmp_val &= reg->binary_data_fine_mask; + } + reg_val |= tmp_val; + + /* + * NOTE: we leave the iodelay values unlocked - this is to work around + * situations such as those found with mmc mode change. + * However, this leaves open any unwarranted changes to padconf register + * impacting iodelay configuration. Use with care! + */ + reg_mask |= reg->lock_mask; + reg_val |= reg->unlock_val << __ffs(reg->lock_mask); + r = regmap_update_bits(iod->regmap, val->offset, reg_mask, reg_val); + + dev_dbg(dev, "Set reg 0x%x Delay(a=%d g=%d), Elements(C=%d F=%d)0x%x\n", + val->offset, val->a_delay, val->g_delay, c_elements, + f_elements, reg_val); + + return r; +} + +/** + * ti_iodelay_pinconf_init_dev() - Initialize IODelay device + * @iod: IODelay device + * + * Unlocks the IODelay region, computes the common parameters + * + * Return: 0 in case of success, else appropriate error value + */ +static int ti_iodelay_pinconf_init_dev(struct ti_iodelay_device *iod) +{ + const struct ti_iodelay_reg_data *reg = iod->reg_data; + struct device *dev = iod->dev; + struct ti_iodelay_reg_values *ival = &iod->reg_init_conf_values; + u32 val; + int r; + + /* unlock the IOdelay region */ + r = regmap_update_bits(iod->regmap, reg->reg_global_lock_offset, + reg->global_lock_mask, reg->global_unlock_val); + if (r) + return r; + + /* Read up Recalibration sequence done by bootloader */ + r = regmap_read(iod->regmap, reg->reg_refclk_offset, &val); + if (r) + return r; + ival->ref_clk_period = ti_iodelay_extract(val, reg->refclk_period_mask); + dev_dbg(dev, "refclk_period=0x%04x\n", ival->ref_clk_period); + + r = regmap_read(iod->regmap, reg->reg_coarse_offset, &val); + if (r) + return r; + ival->coarse_ref_count = + ti_iodelay_extract(val, reg->coarse_ref_count_mask); + ival->coarse_delay_count = + ti_iodelay_extract(val, reg->coarse_delay_count_mask); + if (!ival->coarse_delay_count) { + dev_err(dev, "Invalid Coarse delay count (0) (reg=0x%08x)\n", + val); + return -EINVAL; + } + ival->cdpe = ti_iodelay_compute_dpe(ival->ref_clk_period, + ival->coarse_ref_count, + ival->coarse_delay_count, 88); + if (!ival->cdpe) { + dev_err(dev, "Invalid cdpe computed params = %d %d %d\n", + ival->ref_clk_period, ival->coarse_ref_count, + ival->coarse_delay_count); + return -EINVAL; + } + dev_dbg(iod->dev, "coarse: ref=0x%04x delay=0x%04x cdpe=0x%08x\n", + ival->coarse_ref_count, ival->coarse_delay_count, ival->cdpe); + + r = regmap_read(iod->regmap, reg->reg_fine_offset, &val); + if (r) + return r; + ival->fine_ref_count = + ti_iodelay_extract(val, reg->fine_ref_count_mask); + ival->fine_delay_count = + ti_iodelay_extract(val, reg->fine_delay_count_mask); + if (!ival->fine_delay_count) { + dev_err(dev, "Invalid Fine delay count (0) (reg=0x%08x)\n", + val); + return -EINVAL; + } + ival->fdpe = ti_iodelay_compute_dpe(ival->ref_clk_period, + ival->fine_ref_count, + ival->fine_delay_count, 264); + if (!ival->fdpe) { + dev_err(dev, "Invalid fdpe(0) computed params = %d %d %d\n", + ival->ref_clk_period, ival->fine_ref_count, + ival->fine_delay_count); + return -EINVAL; + } + dev_dbg(iod->dev, "fine: ref=0x%04x delay=0x%04x fdpe=0x%08x\n", + ival->fine_ref_count, ival->fine_delay_count, ival->fdpe); + + return 0; +} + +/** + * ti_iodelay_pinconf_deinit_dev() - deinit the IOdelay device + * @iod: IODelay device + * + * Deinitialize the IODelay device (basically just lock the region back up. + */ +static void ti_iodelay_pinconf_deinit_dev(struct ti_iodelay_device *iod) +{ + const struct ti_iodelay_reg_data *reg = iod->reg_data; + + /* lock the IOdelay region back again */ + regmap_update_bits(iod->regmap, reg->reg_global_lock_offset, + reg->global_lock_mask, reg->global_lock_val); +} + +/*--- Pinctrl/pinconf framework stuff ----*/ + +/** + * ti_iodelay_get_group() - Find the group mapped by a group selector + * @iod: IODelay device + * @gselector: Group Selector + * + * Return: Corresponding group representing group selector in list of groups + * managed in IOdelay device OR NULL if not found. + */ +static struct ti_iodelay_pingroup *ti_iodelay_get_group(struct ti_iodelay_device + *iod, + unsigned gselector) +{ + struct ti_iodelay_pingroup *group; + int gid = 0; + + list_for_each_entry(group, &iod->groups, node) { + if (gid == gselector) + return group; + gid++; + } + + dev_err(iod->dev, "%s could not find pingroup %i\n", __func__, + gselector); + return NULL; +} + +/** + * ti_iodelay_dt_node_to_map() - Map a device tree node to appropriate group + * @pctldev: pinctrl device representing IODelay device + * @np: Node Pointer (device tree) + * @map: Pinctrl Map returned back to pinctrl framework + * @num_maps: Number of maps (1) + * + * Maps the device tree description into a group of configuration parameters + * for IOdelay block entry. + * + * Return: 0 in case of success, else appropriate error value + */ +static int ti_iodelay_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np, + struct pinctrl_map **map, + unsigned *num_maps) +{ + struct ti_iodelay_device *iod; + struct device *dev; + /* const char **pgnames; */ + int ret = 0; + const __be32 *mux; + struct ti_iodelay_conf_vals *vals; + struct ti_iodelay_pingroup *group; + int size, index, idx, rows; + u32 offset, val; + + iod = pinctrl_dev_get_drvdata(pctldev); + if (!iod) + return -EINVAL; + dev = iod->dev; + + *map = devm_kzalloc(dev, sizeof(**map), GFP_KERNEL); + if (!*map) + return -ENOMEM; + *num_maps = 0; + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (!group) { + ret = -ENOMEM; + goto free_map; + } + + mux = of_get_property(np, IODELAY_MUX_PINS_NAME, &size); + if ((!mux) || (size < sizeof(*mux) * 2)) { + dev_err(dev, "bad data for mux %s\n", np->name); + ret = -EINVAL; + goto free_group; + } + + size /= sizeof(*mux); /* Number of elements in array */ + rows = size / 2; + + vals = devm_kzalloc(dev, sizeof(*vals) * rows, GFP_KERNEL); + if (!vals) { + ret = -ENOMEM; + goto free_group; + } + + index = 0; + idx = 0; + while (index < size) { + offset = be32_to_cpup(mux + index++); + val = be32_to_cpup(mux + index++); + vals[idx].offset = offset; + vals[idx].a_delay = val & 0xFFFF; + vals[idx].g_delay = (val & 0xFFFF0000) >> 16; + if (offset > iod->reg_data->regmap_config->max_register) { + dev_err(dev, "Invalid offset for %s 0x%x\n", + np->name, offset); + break; + } + dev_dbg(dev, "%s offset=%x a_delay = %d g_delay = %d\n", + np->name, vals[idx].offset, vals[idx].a_delay, + vals[idx].g_delay); + idx++; + } + + group->name = np->name; + group->np = np; + group->vals = vals; + group->nvals = idx; + group->config = PIN_CONFIG_END; + group->map = *map; + + /* Add to group list */ + mutex_lock(&iod->mutex); + list_add_tail(&group->node, &iod->groups); + iod->ngroups++; + mutex_unlock(&iod->mutex); + + (*map)->type = PIN_MAP_TYPE_CONFIGS_GROUP; + (*map)->data.configs.group_or_pin = np->name; + (*map)->data.configs.configs = &group->config; + (*map)->data.configs.num_configs = 1; + *num_maps = 1; + + return 0; + +free_group: + devm_kfree(dev, group); +free_map: + devm_kfree(dev, *map); + return ret; +} + +/** + * ti_iodelay_dt_free_map() - Free map and resource alloted as per the map + * @pctldev: pinctrl device representing IODelay device + * @map: Map allocated by ti_iodelay_dt_node_to_map + * @num_maps: Num maps (1) + * + * Removes the group associated with the map and frees all resources allocated + * for the group. + */ +static void ti_iodelay_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, unsigned num_maps) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + bool found = false; + + if (!map) + return; + + iod = pinctrl_dev_get_drvdata(pctldev); + if (!iod) + return; + dev = iod->dev; + + mutex_lock(&iod->mutex); + list_for_each_entry(group, &iod->groups, node) { + if (group->map == map) { + found = true; + list_del(&group->node); + iod->ngroups--; + break; + } + } + mutex_unlock(&iod->mutex); + + /* If some freaky pinconf framework bug... */ + if (!found) + return; + + devm_kfree(dev, group->vals); + devm_kfree(dev, group); + devm_kfree(dev, map); +} + +/** + * ti_iodelay_pinctrl_get_groups_count() - Get number of groups registered + * @pctldev: pinctrl device representing IODelay device + * + * Return: number of groups mapped on the IODelay + */ +static int ti_iodelay_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct ti_iodelay_device *iod; + struct device *dev; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + + return iod->ngroups; +} + +/** + * ti_iodelay_pinctrl_get_group_name() - Get the group name + * @pctldev: pinctrl device representing IODelay device + * @gselector: group selector + * + * Return: name of the Group given a valid gselector, else NULL. + */ +static const char *ti_iodelay_pinctrl_get_group_name(struct pinctrl_dev + *pctldev, + unsigned gselector) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + + group = ti_iodelay_get_group(iod, gselector); + if (!group) + return NULL; + + return group->name; +} + +/** + * ti_iodelay_pinctrl_get_group_pins() - get group pins + * @pctldev: pinctrl device representing IODelay device + * @gselector: Group selector + * @pins: pointer to the pins + * @npins: number of pins + * + * Dummy implementation since we do not track pins, we track configurations + * Forced by pinctrl's pinctrl_check_ops() + * + * Return: -EINVAL + */ +static int ti_iodelay_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, + unsigned gselector, + const unsigned **pins, + unsigned *npins) +{ + /* Dummy implementation - we dont do pin mux */ + return -EINVAL; +} + +/** + * ti_iodelay_pinconf_group_get() - Get the group configuration + * @pctldev: pinctrl device representing IODelay device + * @gselector: Group selector + * @config: configuration returned + * + * Return: The configuration if the group is valid, else returns -EINVAL + */ +static int ti_iodelay_pinconf_group_get(struct pinctrl_dev *pctldev, + unsigned gselector, + unsigned long *config) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + + if (!group) + return -EINVAL; + + *config = group->config; + return 0; +} + +/** + * ti_iodelay_pinconf_group_set() - Configure the groups of pins + * @pctldev: pinctrl device representing IODelay device + * @gselector: Group selector + * @configs: Configurations + * @num_configs: Number of configurations + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_pinconf_group_set(struct pinctrl_dev *pctldev, + unsigned gselector, + unsigned long *configs, + unsigned num_configs) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + int i; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + + if (num_configs != 1) { + dev_err(dev, "Unsupported number of configurations %d\n", + num_configs); + return -EINVAL; + } + + if (*configs != PIN_CONFIG_END) { + dev_err(dev, "Unsupported configuration\n"); + return -EINVAL; + } + + for (i = 0; i < group->nvals; i++) { + if (ti_iodelay_pinconf_set(iod, &group->vals[i])) + return -ENOTSUPP; + } + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +/** + * ti_iodelay_pinconf_group_dbg_show() - show the group information + * @pctldev: Show the group information + * @s: Sequence file + * @gselector: group selector + * + * Provide the configuration information of the selected group + */ +static void ti_iodelay_pinconf_group_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned gselector) +{ + struct ti_iodelay_device *iod; + struct device *dev; + struct ti_iodelay_pingroup *group; + int i; + + iod = pinctrl_dev_get_drvdata(pctldev); + dev = iod->dev; + group = ti_iodelay_get_group(iod, gselector); + if (!group) + return; + + for (i = 0; i < group->nvals; i++) { + struct ti_iodelay_conf_vals *val; + u32 reg = 0; + + val = &group->vals[i]; + regmap_read(iod->regmap, val->offset, ®), + seq_printf(s, "\n\t0x%08x = 0x%08x (%3d, %3d)", + val->offset, reg, val->a_delay, val->g_delay); + } +} +#endif + +static struct pinctrl_ops ti_iodelay_pinctrl_ops = { + .dt_node_to_map = ti_iodelay_dt_node_to_map, + .dt_free_map = ti_iodelay_dt_free_map, + .get_groups_count = ti_iodelay_pinctrl_get_groups_count, + .get_group_name = ti_iodelay_pinctrl_get_group_name, + .get_group_pins = ti_iodelay_pinctrl_get_group_pins, +}; + +static struct pinconf_ops ti_iodelay_pinctrl_pinconf_ops = { + .pin_config_group_get = ti_iodelay_pinconf_group_get, + .pin_config_group_set = ti_iodelay_pinconf_group_set, +#ifdef CONFIG_DEBUG_FS + .pin_config_group_dbg_show = ti_iodelay_pinconf_group_dbg_show, +#endif +}; + +/** + * ti_iodelay_alloc_pins() - Allocate structures needed for pins for IOdelay + * @dev: device pointer + * @iod: IODelay device + * @base_phy: Base Physical Address + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_alloc_pins(struct device *dev, + struct ti_iodelay_device *iod, u32 base_phy) +{ + const struct ti_iodelay_reg_data *r = iod->reg_data; + struct pinctrl_pin_desc *pin; + struct ti_iodelay_pin_name *pn; + u32 phy_reg; + int nr_pins, i; + + nr_pins = (r->regmap_config->max_register - r->reg_start_offset) / 4; + + dev_dbg(dev, "Allocating %i pins\n", nr_pins); + + iod->pa = devm_kzalloc(dev, sizeof(*iod->pa) * nr_pins, GFP_KERNEL); + if (!iod->pa) + return -ENOMEM; + + iod->names = + devm_kzalloc(dev, sizeof(struct ti_iodelay_pin_name) * nr_pins, + GFP_KERNEL); + if (!iod->names) + return -ENOMEM; + + iod->desc.pins = iod->pa; + iod->desc.npins = nr_pins; + + phy_reg = r->reg_start_offset + base_phy; + pn = iod->names; + for (i = 0; i < nr_pins; i++, pn++, phy_reg += 4) { + pin = &iod->pa[i]; + sprintf(pn->name, "%x.%d", phy_reg, i); + pin->number = i; + pin->name = pn->name; + } + + return 0; +} + +/** + * ti_iodelay_probe() - Standard probe + * @pdev: platform device + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = of_node_get(dev->of_node); + const struct of_device_id *match; + struct resource *res; + struct ti_iodelay_device *iod; + int ret = 0; + + if (!np) { + ret = -EINVAL; + dev_err(dev, "No OF node\n"); + goto exit_out; + } + + match = of_match_device(ti_iodelay_of_match, dev); + if (!match) { + ret = -EINVAL; + dev_err(dev, "No DATA match\n"); + goto exit_out; + } + + iod = devm_kzalloc(dev, sizeof(*iod), GFP_KERNEL); + if (!iod) { + ret = -ENOMEM; + goto exit_out; + } + iod->dev = dev; + iod->reg_data = match->data; + + /* So far We can assume there is only 1 bank of registers */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Missing MEM resource\n"); + ret = -ENODEV; + goto exit_out; + } + + iod->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(iod->reg_base)) { + ret = PTR_ERR(iod->reg_base); + goto exit_out; + } + + iod->regmap = devm_regmap_init_mmio(dev, iod->reg_base, + iod->reg_data->regmap_config); + if (IS_ERR(iod->regmap)) { + dev_err(dev, "Regmap MMIO init failed.\n"); + ret = PTR_ERR(iod->regmap); + goto exit_out; + } + + if (ti_iodelay_pinconf_init_dev(iod)) + goto exit_out; + + ret = ti_iodelay_alloc_pins(dev, iod, res->start); + if (ret) + goto exit_out; + + INIT_LIST_HEAD(&iod->groups); + mutex_init(&iod->mutex); + + iod->desc.pctlops = &ti_iodelay_pinctrl_ops; + /* no pinmux ops - we are pinconf */ + iod->desc.confops = &ti_iodelay_pinctrl_pinconf_ops; + iod->desc.name = dev_name(dev); + iod->desc.owner = THIS_MODULE; + + iod->pctl = pinctrl_register(&iod->desc, dev, iod); + if (!iod->pctl) { + dev_err(dev, "Failed to register pinctrl\n"); + ret = -ENODEV; + goto exit_out; + } + + platform_set_drvdata(pdev, iod); + +exit_out: + of_node_put(np); + return ret; +} + +/** + * ti_iodelay_remove() - standard remove + * @pdev: platform device + * + * Return: 0 if all went fine, else appropriate error value. + */ +static int ti_iodelay_remove(struct platform_device *pdev) +{ + struct ti_iodelay_device *iod = platform_get_drvdata(pdev); + + if (!iod) + return 0; + if (iod->pctl) + pinctrl_unregister(iod->pctl); + + ti_iodelay_pinconf_deinit_dev(iod); + + /* Expect other allocations to be freed by devm */ + + return 0; +} + +static struct regmap_config dra7_iodelay_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xD1C, +}; + +static struct ti_iodelay_reg_data dra7_iodelay_data = { + .signature_mask = 0x0003F000, + .signature_value = 0x29, + .lock_mask = 0x00000400, + .lock_val = 1, + .unlock_val = 0, + .binary_data_coarse_mask = 0x000003E0, + .binary_data_fine_mask = 0x0000001F, + + .reg_refclk_offset = 0x14, + .refclk_period_mask = 0xFFFF, + + .reg_coarse_offset = 0x18, + .coarse_delay_count_mask = 0xFFFF0000, + .coarse_ref_count_mask = 0x0000FFFF, + + .reg_fine_offset = 0x1C, + .fine_delay_count_mask = 0xFFFF0000, + .fine_ref_count_mask = 0x0000FFFF, + + .reg_global_lock_offset = 0x2C, + .global_lock_mask = 0x0000FFFF, + .global_unlock_val = 0x0000AAAA, + .global_lock_val = 0x0000AAAB, + + .reg_start_offset = 0x30, + .regmap_config = &dra7_iodelay_regmap_config, +}; + +static const struct of_device_id ti_iodelay_of_match[] = { + {.compatible = "ti,dra7-iodelay", .data = &dra7_iodelay_data}, + { /* Hopefully no more.. */ }, +}; +MODULE_DEVICE_TABLE(of, ti_iodelay_of_match); + +static struct platform_driver ti_iodelay_driver = { + .probe = ti_iodelay_probe, + .remove = ti_iodelay_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = ti_iodelay_of_match, + }, +}; +module_platform_driver(ti_iodelay_driver); + +MODULE_AUTHOR("Texas Instruments, Inc."); +MODULE_DESCRIPTION("Pinconf driver for TI's IO Delay module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 2f4641a..3deb642 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -270,6 +270,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_OMAP_DMTIMER + tristate "OMAP Dual-Mode Timer PWM support" + depends on OF && ARCH_OMAP && OMAP_DM_TIMER + help + Generic PWM framework driver for OMAP Dual-Mode Timer PWM output + + To compile this driver as a module, choose M here: the module + will be called pwm-omap-dmtimer + config PWM_PCA9685 tristate "NXP PCA9685 PWM driver" depends on I2C diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 69b8275..dd35bc1 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_PWM_LPSS_PCI) += pwm-lpss-pci.o obj-$(CONFIG_PWM_LPSS_PLATFORM) += pwm-lpss-platform.o obj-$(CONFIG_PWM_MTK_DISP) += pwm-mtk-disp.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o diff --git b/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c new file mode 100644 index 0000000..b7e6ecb --- /dev/null +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2015 Neil Armstrong + * Copyright (c) 2014 Joachim Eastwood + * Copyright (c) 2012 NeilBrown + * Heavily based on earlier code which is: + * Copyright (c) 2010 Grant Erickson + * + * Also based on pwm-samsung.c + * + * 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. + * + * Description: + * This file is the core OMAP support for the generic, Linux + * PWM driver / controller, using the OMAP's dual-mode timers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DM_TIMER_LOAD_MIN 0xfffffffe +#define DM_TIMER_MAX 0xffffffff + +struct pwm_omap_dmtimer_chip { + struct pwm_chip chip; + struct mutex mutex; + pwm_omap_dmtimer *dm_timer; + struct pwm_omap_dmtimer_pdata *pdata; + struct platform_device *dm_timer_pdev; +}; + +static inline struct pwm_omap_dmtimer_chip * +to_pwm_omap_dmtimer_chip(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_omap_dmtimer_chip, chip); +} + +static u32 pwm_omap_dmtimer_get_clock_cycles(unsigned long clk_rate, int ns) +{ + return DIV_ROUND_CLOSEST_ULL((u64)clk_rate * ns, NSEC_PER_SEC); +} + +static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap) +{ + /* + * According to OMAP 4 TRM section 22.2.4.10 the counter should be + * started at 0xFFFFFFFE when overflow and match is used to ensure + * that the PWM line is toggled on the first event. + * + * Note that omap_dm_timer_enable/disable is for register access and + * not the timer counter itself. + */ + omap->pdata->enable(omap->dm_timer); + omap->pdata->write_counter(omap->dm_timer, DM_TIMER_LOAD_MIN); + omap->pdata->disable(omap->dm_timer); + + omap->pdata->start(omap->dm_timer); +} + +static int pwm_omap_dmtimer_enable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + mutex_lock(&omap->mutex); + pwm_omap_dmtimer_start(omap); + mutex_unlock(&omap->mutex); + + return 0; +} + +static void pwm_omap_dmtimer_disable(struct pwm_chip *chip, + struct pwm_device *pwm) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + mutex_lock(&omap->mutex); + omap->pdata->stop(omap->dm_timer); + mutex_unlock(&omap->mutex); +} + +static int pwm_omap_dmtimer_config(struct pwm_chip *chip, + struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + u32 period_cycles, duty_cycles; + u32 load_value, match_value; + struct clk *fclk; + unsigned long clk_rate; + bool timer_active; + + dev_dbg(chip->dev, "requested duty cycle: %d ns, period: %d ns\n", + duty_ns, period_ns); + + mutex_lock(&omap->mutex); + if (duty_ns == pwm_get_duty_cycle(pwm) && + period_ns == pwm_get_period(pwm)) { + /* No change - don't cause any transients. */ + mutex_unlock(&omap->mutex); + return 0; + } + + fclk = omap->pdata->get_fclk(omap->dm_timer); + if (!fclk) { + dev_err(chip->dev, "invalid pmtimer fclk\n"); + goto err_einval; + } + + clk_rate = clk_get_rate(fclk); + if (!clk_rate) { + dev_err(chip->dev, "invalid pmtimer fclk rate\n"); + goto err_einval; + } + + dev_dbg(chip->dev, "clk rate: %luHz\n", clk_rate); + + /* + * Calculate the appropriate load and match values based on the + * specified period and duty cycle. The load value determines the + * period time and the match value determines the duty time. + * + * The period lasts for (DM_TIMER_MAX-load_value+1) clock cycles. + * Similarly, the active time lasts (match_value-load_value+1) cycles. + * The non-active time is the remainder: (DM_TIMER_MAX-match_value) + * clock cycles. + * + * NOTE: It is required that: load_value <= match_value < DM_TIMER_MAX + * + * References: + * OMAP4430/60/70 TRM sections 22.2.4.10 and 22.2.4.11 + * AM335x Sitara TRM sections 20.1.3.5 and 20.1.3.6 + */ + period_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, period_ns); + duty_cycles = pwm_omap_dmtimer_get_clock_cycles(clk_rate, duty_ns); + + if (period_cycles < 2) { + dev_info(chip->dev, + "period %d ns too short for clock rate %lu Hz\n", + period_ns, clk_rate); + goto err_einval; + } + + if (duty_cycles < 1) { + dev_dbg(chip->dev, + "duty cycle %d ns is too short for clock rate %lu Hz\n", + duty_ns, clk_rate); + dev_dbg(chip->dev, "using minimum of 1 clock cycle\n"); + duty_cycles = 1; + } else if (duty_cycles >= period_cycles) { + dev_dbg(chip->dev, + "duty cycle %d ns is too long for period %d ns at clock rate %lu Hz\n", + duty_ns, period_ns, clk_rate); + dev_dbg(chip->dev, "using maximum of 1 clock cycle less than period\n"); + duty_cycles = period_cycles - 1; + } + + dev_dbg(chip->dev, "effective duty cycle: %lld ns, period: %lld ns\n", + DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * duty_cycles, + clk_rate), + DIV_ROUND_CLOSEST_ULL((u64)NSEC_PER_SEC * period_cycles, + clk_rate)); + + load_value = (DM_TIMER_MAX - period_cycles) + 1; + match_value = load_value + duty_cycles - 1; + + /* + * We MUST stop the associated dual-mode timer before attempting to + * write its registers, but calls to omap_dm_timer_start/stop must + * be balanced so check if timer is active before calling timer_stop. + */ + timer_active = pm_runtime_active(&omap->dm_timer_pdev->dev); + if (timer_active) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->set_load(omap->dm_timer, true, load_value); + omap->pdata->set_match(omap->dm_timer, true, match_value); + + dev_dbg(chip->dev, "load value: %#08x (%d), match value: %#08x (%d)\n", + load_value, load_value, match_value, match_value); + + omap->pdata->set_pwm(omap->dm_timer, + pwm->polarity == PWM_POLARITY_INVERSED, + true, + PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); + + /* If config was called while timer was running it must be reenabled. */ + if (timer_active) + pwm_omap_dmtimer_start(omap); + + mutex_unlock(&omap->mutex); + + return 0; + +err_einval: + mutex_unlock(&omap->mutex); + + return -EINVAL; +} + +static int pwm_omap_dmtimer_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + /* + * PWM core will not call set_polarity while PWM is enabled so it's + * safe to reconfigure the timer here without stopping it first. + */ + mutex_lock(&omap->mutex); + omap->pdata->set_pwm(omap->dm_timer, + polarity == PWM_POLARITY_INVERSED, + true, + PWM_OMAP_DMTIMER_TRIGGER_OVERFLOW_AND_COMPARE); + mutex_unlock(&omap->mutex); + + return 0; +} + +static const struct pwm_ops pwm_omap_dmtimer_ops = { + .enable = pwm_omap_dmtimer_enable, + .disable = pwm_omap_dmtimer_disable, + .config = pwm_omap_dmtimer_config, + .set_polarity = pwm_omap_dmtimer_set_polarity, + .owner = THIS_MODULE, +}; + +static int pwm_omap_dmtimer_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *timer; + struct pwm_omap_dmtimer_chip *omap; + struct pwm_omap_dmtimer_pdata *pdata; + pwm_omap_dmtimer *dm_timer; + u32 prescaler; + int status; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "Missing dmtimer platform data\n"); + return -EINVAL; + } + + if (!pdata->request_by_node || + !pdata->free || + !pdata->enable || + !pdata->disable || + !pdata->get_fclk || + !pdata->start || + !pdata->stop || + !pdata->set_load || + !pdata->set_match || + !pdata->set_pwm || + !pdata->set_prescaler || + !pdata->write_counter) { + dev_err(&pdev->dev, "Incomplete dmtimer pdata structure\n"); + return -EINVAL; + } + + timer = of_parse_phandle(np, "ti,timers", 0); + if (!timer) + return -ENODEV; + + if (!of_get_property(timer, "ti,timer-pwm", NULL)) { + dev_err(&pdev->dev, "Missing ti,timer-pwm capability\n"); + return -ENODEV; + } + + dm_timer = pdata->request_by_node(timer); + if (!dm_timer) + return -EPROBE_DEFER; + + omap = devm_kzalloc(&pdev->dev, sizeof(*omap), GFP_KERNEL); + if (!omap) { + pdata->free(dm_timer); + return -ENOMEM; + } + + omap->pdata = pdata; + omap->dm_timer = dm_timer; + + omap->dm_timer_pdev = of_find_device_by_node(timer); + if (!omap->dm_timer_pdev) { + dev_err(&pdev->dev, "Unable to find timer pdev\n"); + omap->pdata->free(dm_timer); + return -EINVAL; + } + + /* + * Ensure that the timer is stopped before we allow PWM core to call + * pwm_enable. + */ + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + /* setup dmtimer prescaler */ + if (!of_property_read_u32(pdev->dev.of_node, "ti,prescaler", + &prescaler)) + omap->pdata->set_prescaler(omap->dm_timer, prescaler); + + omap->chip.dev = &pdev->dev; + omap->chip.ops = &pwm_omap_dmtimer_ops; + omap->chip.base = -1; + omap->chip.npwm = 1; + omap->chip.of_xlate = of_pwm_xlate_with_flags; + omap->chip.of_pwm_n_cells = 3; + + mutex_init(&omap->mutex); + + status = pwmchip_add(&omap->chip); + if (status < 0) { + dev_err(&pdev->dev, "failed to register PWM\n"); + omap->pdata->free(omap->dm_timer); + return status; + } + + platform_set_drvdata(pdev, omap); + + return 0; +} + +static int pwm_omap_dmtimer_remove(struct platform_device *pdev) +{ + struct pwm_omap_dmtimer_chip *omap = platform_get_drvdata(pdev); + + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + omap->pdata->free(omap->dm_timer); + + mutex_destroy(&omap->mutex); + + return pwmchip_remove(&omap->chip); +} + +static const struct of_device_id pwm_omap_dmtimer_of_match[] = { + {.compatible = "ti,omap-dmtimer-pwm"}, + {} +}; +MODULE_DEVICE_TABLE(of, pwm_omap_dmtimer_of_match); + +static struct platform_driver pwm_omap_dmtimer_driver = { + .driver = { + .name = "omap-dmtimer-pwm", + .of_match_table = of_match_ptr(pwm_omap_dmtimer_of_match), + }, + .probe = pwm_omap_dmtimer_probe, + .remove = pwm_omap_dmtimer_remove, +}; +module_platform_driver(pwm_omap_dmtimer_driver); + +MODULE_AUTHOR("Grant Erickson "); +MODULE_AUTHOR("NeilBrown "); +MODULE_AUTHOR("Neil Armstrong "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("OMAP PWM Driver using Dual-mode Timers"); diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index d0e7dfc..11ce92e 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -713,11 +713,11 @@ static int spidev_probe(struct spi_device *spi) * compatible string, it is a Linux implementation thing * rather than a description of the hardware. */ - if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { - dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); - WARN_ON(spi->dev.of_node && - !of_match_device(spidev_dt_ids, &spi->dev)); - } +// if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { +// dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); +// WARN_ON(spi->dev.of_node && +// !of_match_device(spidev_dt_ids, &spi->dev)); +// } /* Allocate driver data */ spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); diff --git a/drivers/staging/fbtft/Kconfig b/drivers/staging/fbtft/Kconfig index 883ff5b..6f5e824 100644 --- a/drivers/staging/fbtft/Kconfig +++ b/drivers/staging/fbtft/Kconfig @@ -117,12 +117,24 @@ config FB_TFT_SSD1289 help Framebuffer support for SSD1289 +config FB_TFT_SSD1305 + tristate "FB driver for the SSD1305 OLED Controller" + depends on FB_TFT + help + Framebuffer support for SSD1305 + config FB_TFT_SSD1306 tristate "FB driver for the SSD1306 OLED Controller" depends on FB_TFT help Framebuffer support for SSD1306 +config FB_TFT_SSD1325 + tristate "FB driver for the SSD1325 OLED Controller" + depends on FB_TFT + help + Framebuffer support for SSD1305 + config FB_TFT_SSD1331 tristate "FB driver for the SSD1331 LCD Controller" depends on FB_TFT diff --git a/drivers/staging/fbtft/Makefile b/drivers/staging/fbtft/Makefile index 4f9071d..2725ea9 100644 --- a/drivers/staging/fbtft/Makefile +++ b/drivers/staging/fbtft/Makefile @@ -21,7 +21,9 @@ obj-$(CONFIG_FB_TFT_RA8875) += fb_ra8875.o obj-$(CONFIG_FB_TFT_S6D02A1) += fb_s6d02a1.o obj-$(CONFIG_FB_TFT_S6D1121) += fb_s6d1121.o obj-$(CONFIG_FB_TFT_SSD1289) += fb_ssd1289.o +obj-$(CONFIG_FB_TFT_SSD1305) += fb_ssd1305.o obj-$(CONFIG_FB_TFT_SSD1306) += fb_ssd1306.o +obj-$(CONFIG_FB_TFT_SSD1305) += fb_ssd1325.o obj-$(CONFIG_FB_TFT_SSD1331) += fb_ssd1331.o obj-$(CONFIG_FB_TFT_SSD1351) += fb_ssd1351.o obj-$(CONFIG_FB_TFT_ST7735R) += fb_st7735r.o diff --git a/drivers/staging/fbtft/fb_agm1264k-fl.c b/drivers/staging/fbtft/fb_agm1264k-fl.c index 2a50cf9..ba9fc44 100644 --- a/drivers/staging/fbtft/fb_agm1264k-fl.c +++ b/drivers/staging/fbtft/fb_agm1264k-fl.c @@ -272,8 +272,8 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len) int ret = 0; /* buffer to convert RGB565 -> grayscale16 -> Dithered image 1bpp */ - signed short *convert_buf = kmalloc(par->info->var.xres * - par->info->var.yres * sizeof(signed short), GFP_NOIO); + signed short *convert_buf = kmalloc_array(par->info->var.xres * + par->info->var.yres, sizeof(signed short), GFP_NOIO); if (!convert_buf) return -ENOMEM; diff --git a/drivers/staging/fbtft/fb_hx8340bn.c b/drivers/staging/fbtft/fb_hx8340bn.c index e1ed177..9970ed7 100644 --- a/drivers/staging/fbtft/fb_hx8340bn.c +++ b/drivers/staging/fbtft/fb_hx8340bn.c @@ -25,6 +25,7 @@ #include #include #include +#include