diff --git a/.gitignore b/.gitignore index 4ad4a98b884b9..5bc7062332561 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,7 @@ all.config # Kdevelop4 *.kdev4 + +# dtb objects +*.dtb +*.dtbo diff --git a/Documentation/ABI/testing/debugfs-aufs b/Documentation/ABI/testing/debugfs-aufs new file mode 100644 index 0000000000000..99642d1055a29 --- /dev/null +++ b/Documentation/ABI/testing/debugfs-aufs @@ -0,0 +1,50 @@ +What: /debug/aufs/si_/ +Date: March 2009 +Contact: J. R. Okajima +Description: + Under /debug/aufs, a directory named si_ is created + per aufs mount, where is a unique id generated + internally. + +What: /debug/aufs/si_/plink +Date: Apr 2013 +Contact: J. R. Okajima +Description: + It has three lines and shows the information about the + pseudo-link. The first line is a single number + representing a number of buckets. The second line is a + number of pseudo-links per buckets (separated by a + blank). The last line is a single number representing a + total number of psedo-links. + When the aufs mount option 'noplink' is specified, it + will show "1\n0\n0\n". + +What: /debug/aufs/si_/xib +Date: March 2009 +Contact: J. R. Okajima +Description: + It shows the consumed blocks by xib (External Inode Number + Bitmap), its block size and file size. + When the aufs mount option 'noxino' is specified, it + will be empty. About XINO files, see the aufs manual. + +What: /debug/aufs/si_/xino0, xino1 ... xinoN +Date: March 2009 +Contact: J. R. Okajima +Description: + It shows the consumed blocks by xino (External Inode Number + Translation Table), its link count, block size and file + size. + When the aufs mount option 'noxino' is specified, it + will be empty. About XINO files, see the aufs manual. + +What: /debug/aufs/si_/xigen +Date: March 2009 +Contact: J. R. Okajima +Description: + It shows the consumed blocks by xigen (External Inode + Generation Table), its block size and file size. + If CONFIG_AUFS_EXPORT is disabled, this entry will not + be created. + When the aufs mount option 'noxino' is specified, it + will be empty. About XINO files, see the aufs manual. diff --git a/Documentation/ABI/testing/sysfs-aufs b/Documentation/ABI/testing/sysfs-aufs new file mode 100644 index 0000000000000..82f9518495eae --- /dev/null +++ b/Documentation/ABI/testing/sysfs-aufs @@ -0,0 +1,31 @@ +What: /sys/fs/aufs/si_/ +Date: March 2009 +Contact: J. R. Okajima +Description: + Under /sys/fs/aufs, a directory named si_ is created + per aufs mount, where is a unique id generated + internally. + +What: /sys/fs/aufs/si_/br0, br1 ... brN +Date: March 2009 +Contact: J. R. Okajima +Description: + It shows the abolute path of a member directory (which + is called branch) in aufs, and its permission. + +What: /sys/fs/aufs/si_/brid0, brid1 ... bridN +Date: July 2013 +Contact: J. R. Okajima +Description: + It shows the id of a member directory (which is called + branch) in aufs. + +What: /sys/fs/aufs/si_/xi_path +Date: March 2009 +Contact: J. R. Okajima +Description: + It shows the abolute path of XINO (External Inode Number + Bitmap, Translation Table and Generation Table) file + even if it is the default path. + When the aufs mount option 'noxino' is specified, it + will be empty. About XINO files, see the aufs manual. diff --git a/Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr b/Documentation/ABI/testing/sysfs-devices-platform-bone_capemgr new file mode 100644 index 0000000000000..e2df613581234 --- /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 a/Documentation/ABI/testing/sysfs-driver-sunxi-sid b/Documentation/ABI/testing/sysfs-driver-sunxi-sid deleted file mode 100644 index ffb9536f6ecc1..0000000000000 --- a/Documentation/ABI/testing/sysfs-driver-sunxi-sid +++ /dev/null @@ -1,22 +0,0 @@ -What: /sys/devices/*//eeprom -Date: August 2013 -Contact: Oliver Schinagl -Description: read-only access to the SID (Security-ID) on current - A-series SoC's from Allwinner. Currently supports A10, A10s, A13 - and A20 CPU's. The earlier A1x series of SoCs exports 16 bytes, - whereas the newer A20 SoC exposes 512 bytes split into sections. - Besides the 16 bytes of SID, there's also an SJTAG area, - HDMI-HDCP key and some custom keys. Below a quick overview, for - details see the user manual: - 0x000 128 bit root-key (sun[457]i) - 0x010 128 bit boot-key (sun7i) - 0x020 64 bit security-jtag-key (sun7i) - 0x028 16 bit key configuration (sun7i) - 0x02b 16 bit custom-vendor-key (sun7i) - 0x02c 320 bit low general key (sun7i) - 0x040 32 bit read-control access (sun7i) - 0x064 224 bit low general key (sun7i) - 0x080 2304 bit HDCP-key (sun7i) - 0x1a0 768 bit high general key (sun7i) -Users: any user space application which wants to read the SID on - Allwinner's A-series of CPU's. diff --git a/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays b/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays new file mode 100644 index 0000000000000..db8cb196c1325 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-firmware-devicetree-overlays @@ -0,0 +1,34 @@ +What: /sys/firmware/devicetree/overlays/ +Date: March 2015 +Contact: Pantelis Antoniou +Description: + This directory contains the applied device tree overlays of + the running system, as directories of the overlay id. + + enable: 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: March 2015 +Contact: Pantelis Antoniou +Description: + Each directory represents an applied overlay, containing + the following attribute files. + + targets: A file containing the list of targets of each overlay + with each line containing a target. + + can_remove: 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. diff --git a/Documentation/devicetree/bindings/arm/omap/omap.txt b/Documentation/devicetree/bindings/arm/omap/omap.txt index 52985654fb987..18c1d840b7926 100644 --- a/Documentation/devicetree/bindings/arm/omap/omap.txt +++ b/Documentation/devicetree/bindings/arm/omap/omap.txt @@ -24,6 +24,9 @@ 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 a/Documentation/devicetree/bindings/drm/tilcdc/slave.txt b/Documentation/devicetree/bindings/drm/tilcdc/slave.txt deleted file mode 100644 index 3d2c52460dca3..0000000000000 --- a/Documentation/devicetree/bindings/drm/tilcdc/slave.txt +++ /dev/null @@ -1,18 +0,0 @@ -Device-Tree bindings for tilcdc DRM encoder slave output driver - -Required properties: - - compatible: value should be "ti,tilcdc,slave". - - i2c: the phandle for the i2c device the encoder slave is connected to - -Recommended properties: - - pinctrl-names, pinctrl-0: the pincontrol settings to configure - muxing properly for pins that connect to TFP410 device - -Example: - - hdmi { - compatible = "ti,tilcdc,slave"; - i2c = <&i2c0>; - pinctrl-names = "default"; - pinctrl-0 = <&nxp_hdmi_bonelt_pins>; - }; diff --git a/Documentation/devicetree/bindings/drm/tilcdc/tilcdc.txt b/Documentation/devicetree/bindings/drm/tilcdc/tilcdc.txt index fff10da5e927c..2136ee81e061b 100644 --- a/Documentation/devicetree/bindings/drm/tilcdc/tilcdc.txt +++ b/Documentation/devicetree/bindings/drm/tilcdc/tilcdc.txt @@ -18,6 +18,12 @@ Optional properties: - max-pixelclock: The maximum pixel clock that can be supported by the lcd controller in KHz. +Optional nodes: + + - port/ports: to describe a connection to an external encoder. The + binding follows Documentation/devicetree/bindings/graph.txt and + suppors a single port with a single endpoint. + Example: fb: fb@4830e000 { @@ -26,4 +32,25 @@ Example: interrupt-parent = <&intc>; interrupts = <36>; ti,hwmods = "lcdc"; + + port { + lcdc_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; + }; + + tda19988: 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 a/Documentation/devicetree/bindings/misc/bone_capemgr.txt b/Documentation/devicetree/bindings/misc/bone_capemgr.txt new file mode 100644 index 0000000000000..7e4fbc982ceab --- /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 a/Documentation/devicetree/bindings/misc/dra7xx-debugss.txt b/Documentation/devicetree/bindings/misc/dra7xx-debugss.txt new file mode 100644 index 0000000000000..79a3ba844d428 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/dra7xx-debugss.txt @@ -0,0 +1,22 @@ +Texas Instruments Debug Sub-System (DebugSS) module for DRA7xx devices + +Required properties: +- compatible : Should be "ti,dra7xx-debugss" + +Optional properties: +- clocks : the clocks feeding the Debug sub-system. +- clocks-names : clock name strings correspond to the clocks + defined above. These strings are used inside + module source code to identify the respective + clocks + +Documentation: +http://www.ti.com/lit/ds/symlink/dra746.pdf + +Examples: + +debugss: debugss { + compatible = "ti,dra7xx-debugss"; + clocks = <&sys_clkin1>; + clock-names = "sysclockin1"; +}; diff --git a/Documentation/devicetree/bindings/misc/allwinner,sunxi-sid.txt b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt similarity index 78% rename from Documentation/devicetree/bindings/misc/allwinner,sunxi-sid.txt rename to Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt index fabdf64a5737c..d543ed3f53632 100644 --- a/Documentation/devicetree/bindings/misc/allwinner,sunxi-sid.txt +++ b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt @@ -4,6 +4,10 @@ Required properties: - compatible: "allwinner,sun4i-a10-sid" or "allwinner,sun7i-a20-sid" - reg: Should contain registers location and length += Data cells = +Are child nodes of qfprom, bindings of which as described in +bindings/nvmem/nvmem.txt + Example for sun4i: sid@01c23800 { compatible = "allwinner,sun4i-a10-sid"; diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt new file mode 100644 index 0000000000000..383d5889e95a3 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.txt @@ -0,0 +1,20 @@ +Freescale i.MX6 On-Chip OTP Controller (OCOTP) device tree bindings + +This binding represents the on-chip eFuse OTP controller found on +i.MX6Q/D, i.MX6DL/S, i.MX6SL, and i.MX6SX SoCs. + +Required properties: +- compatible: should be one of + "fsl,imx6q-ocotp" (i.MX6Q/D/DL/S), + "fsl,imx6sl-ocotp" (i.MX6SL), or + "fsl,imx6sx-ocotp" (i.MX6SX), followed by "syscon". +- reg: Should contain the register base and length. +- clocks: Should contain a phandle pointing to the gated peripheral clock. + +Example: + + ocotp: ocotp@021bc000 { + compatible = "fsl,imx6q-ocotp", "syscon"; + reg = <0x021bc000 0x4000>; + clocks = <&clks IMX6QDL_CLK_IIM>; + }; diff --git a/Documentation/devicetree/bindings/nvmem/mxs-ocotp.txt b/Documentation/devicetree/bindings/nvmem/mxs-ocotp.txt new file mode 100644 index 0000000000000..daebce9e6b07f --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/mxs-ocotp.txt @@ -0,0 +1,25 @@ +On-Chip OTP Memory for Freescale i.MX23/i.MX28 + +Required properties : +- compatible : + - "fsl,imx23-ocotp" for i.MX23 + - "fsl,imx28-ocotp" for i.MX28 +- #address-cells : Should be 1 +- #size-cells : Should be 1 +- reg : Address and length of OTP controller registers +- clocks : Should contain a reference to the hbus clock + += Data cells = +Are child nodes of mxs-ocotp, bindings of which as described in +bindings/nvmem/nvmem.txt + +Example for i.MX28: + + ocotp: ocotp@8002c000 { + compatible = "fsl,imx28-ocotp", "fsl,ocotp"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x8002c000 0x2000>; + clocks = <&clks 25>; + status = "okay"; + }; diff --git a/Documentation/devicetree/bindings/nvmem/nvmem.txt b/Documentation/devicetree/bindings/nvmem/nvmem.txt new file mode 100644 index 0000000000000..b52bc11e95972 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/nvmem.txt @@ -0,0 +1,80 @@ += NVMEM(Non Volatile Memory) Data Device Tree Bindings = + +This binding is intended to represent the location of hardware +configuration data stored in NVMEMs like eeprom, efuses and so on. + +On a significant proportion of boards, the manufacturer has stored +some data on NVMEM, for the OS to be able to retrieve these information +and act upon it. Obviously, the OS has to know about where to retrieve +these data from, and where they are stored on the storage device. + +This document is here to document this. + += Data providers = +Contains bindings specific to provider drivers and data cells as children +of this node. + +Optional properties: + read-only: Mark the provider as read only. + += Data cells = +These are the child nodes of the provider which contain data cell +information like offset and size in nvmem provider. + +Required properties: +reg: specifies the offset in byte within the storage device. + +Optional properties: + +bits: Is pair of bit location and number of bits, which specifies offset + in bit and number of bits within the address range specified by reg property. + Offset takes values from 0-7. + +For example: + + /* Provider */ + qfprom: qfprom@00700000 { + ... + + /* Data cells */ + tsens_calibration: calib@404 { + reg = <0x404 0x10>; + }; + + tsens_calibration_bckp: calib_bckp@504 { + reg = <0x504 0x11>; + bits = <6 128> + }; + + pvs_version: pvs-version@6 { + reg = <0x6 0x2> + bits = <7 2> + }; + + speed_bin: speed-bin@c{ + reg = <0xc 0x1>; + bits = <2 3>; + + }; + ... + }; + += Data consumers = +Are device nodes which consume nvmem data cells/providers. + +Required-properties: +nvmem-cells: list of phandle to the nvmem data cells. +nvmem-cell-names: names for the each nvmem-cells specified. Required if + nvmem-cells is used. + +Optional-properties: +nvmem : list of phandles to nvmem providers. +nvmem-names: names for the each nvmem provider. required if nvmem is used. + +For example: + + tsens { + ... + nvmem-cells = <&tsens_calibration>; + nvmem-cell-names = "calibration"; + }; diff --git a/Documentation/devicetree/bindings/nvmem/qfprom.txt b/Documentation/devicetree/bindings/nvmem/qfprom.txt new file mode 100644 index 0000000000000..4ad68b7f5c18b --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/qfprom.txt @@ -0,0 +1,35 @@ += Qualcomm QFPROM device tree bindings = + +This binding is intended to represent QFPROM which is found in most QCOM SOCs. + +Required properties: +- compatible: should be "qcom,qfprom" +- reg: Should contain registers location and length + += Data cells = +Are child nodes of qfprom, bindings of which as described in +bindings/nvmem/nvmem.txt + +Example: + + qfprom: qfprom@00700000 { + compatible = "qcom,qfprom"; + reg = <0x00700000 0x8000>; + ... + /* Data cells */ + tsens_calibration: calib@404 { + reg = <0x4404 0x10>; + }; + }; + + += Data consumers = +Are device nodes which consume nvmem data cells. + +For example: + + tsens { + ... + nvmem-cells = <&tsens_calibration>; + nvmem-cell-names = "calibration"; + }; diff --git a/Documentation/devicetree/bindings/nvmem/rockchip-efuse.txt b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.txt new file mode 100644 index 0000000000000..8f86ab3b10468 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/rockchip-efuse.txt @@ -0,0 +1,38 @@ += Rockchip eFuse device tree bindings = + +Required properties: +- compatible: Should be "rockchip,rockchip-efuse" +- reg: Should contain the registers location and exact eFuse size +- clocks: Should be the clock id of eFuse +- clock-names: Should be "pclk_efuse" + += Data cells = +Are child nodes of eFuse, bindings of which as described in +bindings/nvmem/nvmem.txt + +Example: + + efuse: efuse@ffb40000 { + compatible = "rockchip,rockchip-efuse"; + reg = <0xffb40000 0x20>; + #address-cells = <1>; + #size-cells = <1>; + clocks = <&cru PCLK_EFUSE256>; + clock-names = "pclk_efuse"; + + /* Data cells */ + cpu_leakage: cpu_leakage { + reg = <0x17 0x1>; + }; + }; + += Data consumers = +Are device nodes which consume nvmem data cells. + +Example: + + cpu_leakage { + ... + nvmem-cells = <&cpu_leakage>; + nvmem-cell-names = "cpu_leakage"; + }; diff --git a/Documentation/devicetree/bindings/nvmem/vf610-ocotp.txt b/Documentation/devicetree/bindings/nvmem/vf610-ocotp.txt new file mode 100644 index 0000000000000..56ed481c3e262 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/vf610-ocotp.txt @@ -0,0 +1,19 @@ +On-Chip OTP Memory for Freescale Vybrid + +Required Properties: + compatible: + - "fsl,vf610-ocotp" for VF5xx/VF6xx + #address-cells : Should be 1 + #size-cells : Should be 1 + reg : Address and length of OTP controller and fuse map registers + clocks : ipg clock we associate with the OCOTP peripheral + +Example for Vybrid VF5xx/VF6xx: + + ocotp: ocotp@400a5000 { + compatible = "fsl,vf610-ocotp"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x400a5000 0xCF0>; + clocks = <&clks VF610_CLK_OCOTP>; + }; diff --git a/Documentation/devicetree/configfs-overlays.txt b/Documentation/devicetree/configfs-overlays.txt new file mode 100644 index 0000000000000..5fa43e0643072 --- /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 d418a6ce98120..00ede57d60cfc 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/aufs/README b/Documentation/filesystems/aufs/README new file mode 100644 index 0000000000000..ed1bafe084dcf --- /dev/null +++ b/Documentation/filesystems/aufs/README @@ -0,0 +1,391 @@ + +Aufs4 -- advanced multi layered unification filesystem version 4.x +http://aufs.sf.net +Junjiro R. Okajima + + +0. Introduction +---------------------------------------- +In the early days, aufs was entirely re-designed and re-implemented +Unionfs Version 1.x series. Adding many original ideas, approaches, +improvements and implementations, it becomes totally different from +Unionfs while keeping the basic features. +Recently, Unionfs Version 2.x series begin taking some of the same +approaches to aufs1's. +Unionfs is being developed by Professor Erez Zadok at Stony Brook +University and his team. + +Aufs4 supports linux-4.0 and later, and for linux-3.x series try aufs3. +If you want older kernel version support, try aufs2-2.6.git or +aufs2-standalone.git repository, aufs1 from CVS on SourceForge. + +Note: it becomes clear that "Aufs was rejected. Let's give it up." + According to Christoph Hellwig, linux rejects all union-type + filesystems but UnionMount. + + +PS. Al Viro seems have a plan to merge aufs as well as overlayfs and + UnionMount, and he pointed out an issue around a directory mutex + lock and aufs addressed it. But it is still unsure whether aufs will + be merged (or any other union solution). + + + +1. Features +---------------------------------------- +- unite several directories into a single virtual filesystem. The member + directory is called as a branch. +- you can specify the permission flags to the branch, which are 'readonly', + 'readwrite' and 'whiteout-able.' +- by upper writable branch, internal copyup and whiteout, files/dirs on + readonly branch are modifiable logically. +- dynamic branch manipulation, add, del. +- etc... + +Also there are many enhancements in aufs, such as: +- test only the highest one for the directory permission (dirperm1) +- copyup on open (coo=) +- 'move' policy for copy-up between two writable branches, after + checking free space. +- xattr, acl +- readdir(3) in userspace. +- keep inode number by external inode number table +- keep the timestamps of file/dir in internal copyup operation +- seekable directory, supporting NFS readdir. +- whiteout is hardlinked in order to reduce the consumption of inodes + on branch +- do not copyup, nor create a whiteout when it is unnecessary +- revert a single systemcall when an error occurs in aufs +- remount interface instead of ioctl +- maintain /etc/mtab by an external command, /sbin/mount.aufs. +- loopback mounted filesystem as a branch +- kernel thread for removing the dir who has a plenty of whiteouts +- support copyup sparse file (a file which has a 'hole' in it) +- default permission flags for branches +- selectable permission flags for ro branch, whether whiteout can + exist or not +- export via NFS. +- support /fs/aufs and /aufs. +- support multiple writable branches, some policies to select one + among multiple writable branches. +- a new semantics for link(2) and rename(2) to support multiple + writable branches. +- no glibc changes are required. +- pseudo hardlink (hardlink over branches) +- allow a direct access manually to a file on branch, e.g. bypassing aufs. + including NFS or remote filesystem branch. +- userspace wrapper for pathconf(3)/fpathconf(3) with _PC_LINK_MAX. +- and more... + +Currently these features are dropped temporary from aufs4. +See design/08plan.txt in detail. +- nested mount, i.e. aufs as readonly no-whiteout branch of another aufs + (robr) +- statistics of aufs thread (/sys/fs/aufs/stat) + +Features or just an idea in the future (see also design/*.txt), +- reorder the branch index without del/re-add. +- permanent xino files for NFSD +- an option for refreshing the opened files after add/del branches +- light version, without branch manipulation. (unnecessary?) +- copyup in userspace +- inotify in userspace +- readv/writev + + +2. Download +---------------------------------------- +There are three GIT trees for aufs4, aufs4-linux.git, +aufs4-standalone.git, and aufs-util.git. Note that there is no "4" in +"aufs-util.git." +While the aufs-util is always necessary, you need either of aufs4-linux +or aufs4-standalone. + +The aufs4-linux tree includes the whole linux mainline GIT tree, +git://git.kernel.org/.../torvalds/linux.git. +And you cannot select CONFIG_AUFS_FS=m for this version, eg. you cannot +build aufs4 as an external kernel module. +Several extra patches are not included in this tree. Only +aufs4-standalone tree contains them. They are described in the later +section "Configuration and Compilation." + +On the other hand, the aufs4-standalone tree has only aufs source files +and necessary patches, and you can select CONFIG_AUFS_FS=m. +But you need to apply all aufs patches manually. + +You will find GIT branches whose name is in form of "aufs4.x" where "x" +represents the linux kernel version, "linux-4.x". For instance, +"aufs4.0" is for linux-4.0. For latest "linux-4.x-rcN", use +"aufs4.x-rcN" branch. + +o aufs4-linux tree +$ git clone --reference /your/linux/git/tree \ + git://github.com/sfjro/aufs4-linux.git aufs4-linux.git +- if you don't have linux GIT tree, then remove "--reference ..." +$ cd aufs4-linux.git +$ git checkout origin/aufs4.0 + +Or You may want to directly git-pull aufs into your linux GIT tree, and +leave the patch-work to GIT. +$ cd /your/linux/git/tree +$ git remote add aufs4 git://github.com/sfjro/aufs4-linux.git +$ git fetch aufs4 +$ git checkout -b my4.0 v4.0 +$ (add your local change...) +$ git pull aufs4 aufs4.0 +- now you have v4.0 + your_changes + aufs4.0 in you my4.0 branch. +- you may need to solve some conflicts between your_changes and + aufs4.0. in this case, git-rerere is recommended so that you can + solve the similar conflicts automatically when you upgrade to 4.1 or + later in the future. + +o aufs4-standalone tree +$ git clone git://github.com/sfjro/aufs4-standalone.git aufs4-standalone.git +$ cd aufs4-standalone.git +$ git checkout origin/aufs4.0 + +o aufs-util tree +$ git clone git://git.code.sf.net/p/aufs/aufs-util aufs-util.git +- note that the public aufs-util.git is on SourceForge instead of + GitHUB. +$ cd aufs-util.git +$ git checkout origin/aufs4.0 + +Note: The 4.x-rcN branch is to be used with `rc' kernel versions ONLY. +The minor version number, 'x' in '4.x', of aufs may not always +follow the minor version number of the kernel. +Because changes in the kernel that cause the use of a new +minor version number do not always require changes to aufs-util. + +Since aufs-util has its own minor version number, you may not be +able to find a GIT branch in aufs-util for your kernel's +exact minor version number. +In this case, you should git-checkout the branch for the +nearest lower number. + +For (an unreleased) example: +If you are using "linux-4.10" and the "aufs4.10" branch +does not exist in aufs-util repository, then "aufs4.9", "aufs4.8" +or something numerically smaller is the branch for your kernel. + +Also you can view all branches by + $ git branch -a + + +3. Configuration and Compilation +---------------------------------------- +Make sure you have git-checkout'ed the correct branch. + +For aufs4-linux tree, +- enable CONFIG_AUFS_FS. +- set other aufs configurations if necessary. + +For aufs4-standalone tree, +There are several ways to build. + +1. +- apply ./aufs4-kbuild.patch to your kernel source files. +- apply ./aufs4-base.patch too. +- apply ./aufs4-mmap.patch too. +- apply ./aufs4-standalone.patch too, if you have a plan to set + CONFIG_AUFS_FS=m. otherwise you don't need ./aufs4-standalone.patch. +- copy ./{Documentation,fs,include/uapi/linux/aufs_type.h} files to your + kernel source tree. Never copy $PWD/include/uapi/linux/Kbuild. +- enable CONFIG_AUFS_FS, you can select either + =m or =y. +- and build your kernel as usual. +- install the built kernel. + Note: Since linux-3.9, every filesystem module requires an alias + "fs-". You should make sure that "fs-aufs" is listed in your + modules.aliases file if you set CONFIG_AUFS_FS=m. +- install the header files too by "make headers_install" to the + directory where you specify. By default, it is $PWD/usr. + "make help" shows a brief note for headers_install. +- and reboot your system. + +2. +- module only (CONFIG_AUFS_FS=m). +- apply ./aufs4-base.patch to your kernel source files. +- apply ./aufs4-mmap.patch too. +- apply ./aufs4-standalone.patch too. +- build your kernel, don't forget "make headers_install", and reboot. +- edit ./config.mk and set other aufs configurations if necessary. + Note: You should read $PWD/fs/aufs/Kconfig carefully which describes + every aufs configurations. +- build the module by simple "make". + Note: Since linux-3.9, every filesystem module requires an alias + "fs-". You should make sure that "fs-aufs" is listed in your + modules.aliases file. +- you can specify ${KDIR} make variable which points to your kernel + source tree. +- install the files + + run "make install" to install the aufs module, or copy the built + $PWD/aufs.ko to /lib/modules/... and run depmod -a (or reboot simply). + + run "make install_headers" (instead of headers_install) to install + the modified aufs header file (you can specify DESTDIR which is + available in aufs standalone version's Makefile only), or copy + $PWD/usr/include/linux/aufs_type.h to /usr/include/linux or wherever + you like manually. By default, the target directory is $PWD/usr. +- no need to apply aufs4-kbuild.patch, nor copying source files to your + kernel source tree. + +Note: The header file aufs_type.h is necessary to build aufs-util + as well as "make headers_install" in the kernel source tree. + headers_install is subject to be forgotten, but it is essentially + necessary, not only for building aufs-util. + You may not meet problems without headers_install in some older + version though. + +And then, +- read README in aufs-util, build and install it +- note that your distribution may contain an obsoleted version of + aufs_type.h in /usr/include/linux or something. When you build aufs + utilities, make sure that your compiler refers the correct aufs header + file which is built by "make headers_install." +- if you want to use readdir(3) in userspace or pathconf(3) wrapper, + then run "make install_ulib" too. And refer to the aufs manual in + detail. + +There several other patches in aufs4-standalone.git. They are all +optional. When you meet some problems, they will help you. +- aufs4-loopback.patch + Supports a nested loopback mount in a branch-fs. This patch is + unnecessary until aufs produces a message like "you may want to try + another patch for loopback file". +- vfs-ino.patch + Modifies a system global kernel internal function get_next_ino() in + order to stop assigning 0 for an inode-number. Not directly related to + aufs, but recommended generally. +- tmpfs-idr.patch + Keeps the tmpfs inode number as the lowest value. Effective to reduce + the size of aufs XINO files for tmpfs branch. Also it prevents the + duplication of inode number, which is important for backup tools and + other utilities. When you find aufs XINO files for tmpfs branch + growing too much, try this patch. +- lockdep-debug.patch + Because aufs is not only an ordinary filesystem (callee of VFS), but + also a caller of VFS functions for branch filesystems, subclassing of + the internal locks for LOCKDEP is necessary. LOCKDEP is a debugging + feature of linux kernel. If you enable CONFIG_LOCKDEP, then you will + need to apply this debug patch to expand several constant values. + If don't know what LOCKDEP, then you don't have apply this patch. + + +4. Usage +---------------------------------------- +At first, make sure aufs-util are installed, and please read the aufs +manual, aufs.5 in aufs-util.git tree. +$ man -l aufs.5 + +And then, +$ mkdir /tmp/rw /tmp/aufs +# mount -t aufs -o br=/tmp/rw:${HOME} none /tmp/aufs + +Here is another example. The result is equivalent. +# mount -t aufs -o br=/tmp/rw=rw:${HOME}=ro none /tmp/aufs + Or +# mount -t aufs -o br:/tmp/rw none /tmp/aufs +# mount -o remount,append:${HOME} /tmp/aufs + +Then, you can see whole tree of your home dir through /tmp/aufs. If +you modify a file under /tmp/aufs, the one on your home directory is +not affected, instead the same named file will be newly created under +/tmp/rw. And all of your modification to a file will be applied to +the one under /tmp/rw. This is called the file based Copy on Write +(COW) method. +Aufs mount options are described in aufs.5. +If you run chroot or something and make your aufs as a root directory, +then you need to customize the shutdown script. See the aufs manual in +detail. + +Additionally, there are some sample usages of aufs which are a +diskless system with network booting, and LiveCD over NFS. +See sample dir in CVS tree on SourceForge. + + +5. Contact +---------------------------------------- +When you have any problems or strange behaviour in aufs, please let me +know with: +- /proc/mounts (instead of the output of mount(8)) +- /sys/module/aufs/* +- /sys/fs/aufs/* (if you have them) +- /debug/aufs/* (if you have them) +- linux kernel version + if your kernel is not plain, for example modified by distributor, + the url where i can download its source is necessary too. +- aufs version which was printed at loading the module or booting the + system, instead of the date you downloaded. +- configuration (define/undefine CONFIG_AUFS_xxx) +- kernel configuration or /proc/config.gz (if you have it) +- behaviour which you think to be incorrect +- actual operation, reproducible one is better +- mailto: aufs-users at lists.sourceforge.net + +Usually, I don't watch the Public Areas(Bugs, Support Requests, Patches, +and Feature Requests) on SourceForge. Please join and write to +aufs-users ML. + + +6. Acknowledgements +---------------------------------------- +Thanks to everyone who have tried and are using aufs, whoever +have reported a bug or any feedback. + +Especially donators: +Tomas Matejicek(slax.org) made a donation (much more than once). + Since Apr 2010, Tomas M (the author of Slax and Linux Live + scripts) is making "doubling" donations. + Unfortunately I cannot list all of the donators, but I really + appreciate. + It ends Aug 2010, but the ordinary donation URL is still available. + +Dai Itasaka made a donation (2007/8). +Chuck Smith made a donation (2008/4, 10 and 12). +Henk Schoneveld made a donation (2008/9). +Chih-Wei Huang, ASUS, CTC donated Eee PC 4G (2008/10). +Francois Dupoux made a donation (2008/11). +Bruno Cesar Ribas and Luis Carlos Erpen de Bona, C3SL serves public + aufs2 GIT tree (2009/2). +William Grant made a donation (2009/3). +Patrick Lane made a donation (2009/4). +The Mail Archive (mail-archive.com) made donations (2009/5). +Nippy Networks (Ed Wildgoose) made a donation (2009/7). +New Dream Network, LLC (www.dreamhost.com) made a donation (2009/11). +Pavel Pronskiy made a donation (2011/2). +Iridium and Inmarsat satellite phone retailer (www.mailasail.com), Nippy + Networks (Ed Wildgoose) made a donation for hardware (2011/3). +Max Lekomcev (DOM-TV project) made a donation (2011/7, 12, 2012/3, 6 and +11). +Sam Liddicott made a donation (2011/9). +Era Scarecrow made a donation (2013/4). +Bor Ratajc made a donation (2013/4). +Alessandro Gorreta made a donation (2013/4). +POIRETTE Marc made a donation (2013/4). +Alessandro Gorreta made a donation (2013/4). +lauri kasvandik made a donation (2013/5). +"pemasu from Finland" made a donation (2013/7). +The Parted Magic Project made a donation (2013/9 and 11). +Pavel Barta made a donation (2013/10). +Nikolay Pertsev made a donation (2014/5). +James B made a donation (2014/7 and 2015/7). +Stefano Di Biase made a donation (2014/8). +Daniel Epellei made a donation (2015/1). +OmegaPhil made a donation (2016/1). + +Thank you very much. +Donations are always, including future donations, very important and +helpful for me to keep on developing aufs. + + +7. +---------------------------------------- +If you are an experienced user, no explanation is needed. Aufs is +just a linux filesystem. + + +Enjoy! + +# Local variables: ; +# mode: text; +# End: ; diff --git a/Documentation/filesystems/aufs/design/01intro.txt b/Documentation/filesystems/aufs/design/01intro.txt new file mode 100644 index 0000000000000..784171a567fcd --- /dev/null +++ b/Documentation/filesystems/aufs/design/01intro.txt @@ -0,0 +1,170 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Introduction +---------------------------------------- + +aufs [ei ju: ef es] | [a u f s] +1. abbrev. for "advanced multi-layered unification filesystem". +2. abbrev. for "another unionfs". +3. abbrev. for "auf das" in German which means "on the" in English. + Ex. "Butter aufs Brot"(G) means "butter onto bread"(E). + But "Filesystem aufs Filesystem" is hard to understand. + +AUFS is a filesystem with features: +- multi layered stackable unification filesystem, the member directory + is called as a branch. +- branch permission and attribute, 'readonly', 'real-readonly', + 'readwrite', 'whiteout-able', 'link-able whiteout', etc. and their + combination. +- internal "file copy-on-write". +- logical deletion, whiteout. +- dynamic branch manipulation, adding, deleting and changing permission. +- allow bypassing aufs, user's direct branch access. +- external inode number translation table and bitmap which maintains the + persistent aufs inode number. +- seekable directory, including NFS readdir. +- file mapping, mmap and sharing pages. +- pseudo-link, hardlink over branches. +- loopback mounted filesystem as a branch. +- several policies to select one among multiple writable branches. +- revert a single systemcall when an error occurs in aufs. +- and more... + + +Multi Layered Stackable Unification Filesystem +---------------------------------------------------------------------- +Most people already knows what it is. +It is a filesystem which unifies several directories and provides a +merged single directory. When users access a file, the access will be +passed/re-directed/converted (sorry, I am not sure which English word is +correct) to the real file on the member filesystem. The member +filesystem is called 'lower filesystem' or 'branch' and has a mode +'readonly' and 'readwrite.' And the deletion for a file on the lower +readonly branch is handled by creating 'whiteout' on the upper writable +branch. + +On LKML, there have been discussions about UnionMount (Jan Blunck, +Bharata B Rao and Valerie Aurora) and Unionfs (Erez Zadok). They took +different approaches to implement the merged-view. +The former tries putting it into VFS, and the latter implements as a +separate filesystem. +(If I misunderstand about these implementations, please let me know and +I shall correct it. Because it is a long time ago when I read their +source files last time). + +UnionMount's approach will be able to small, but may be hard to share +branches between several UnionMount since the whiteout in it is +implemented in the inode on branch filesystem and always +shared. According to Bharata's post, readdir does not seems to be +finished yet. +There are several missing features known in this implementations such as +- for users, the inode number may change silently. eg. copy-up. +- link(2) may break by copy-up. +- read(2) may get an obsoleted filedata (fstat(2) too). +- fcntl(F_SETLK) may be broken by copy-up. +- unnecessary copy-up may happen, for example mmap(MAP_PRIVATE) after + open(O_RDWR). + +In linux-3.18, "overlay" filesystem (formerly known as "overlayfs") was +merged into mainline. This is another implementation of UnionMount as a +separated filesystem. All the limitations and known problems which +UnionMount are equally inherited to "overlay" filesystem. + +Unionfs has a longer history. When I started implementing a stackable +filesystem (Aug 2005), it already existed. It has virtual super_block, +inode, dentry and file objects and they have an array pointing lower +same kind objects. After contributing many patches for Unionfs, I +re-started my project AUFS (Jun 2006). + +In AUFS, the structure of filesystem resembles to Unionfs, but I +implemented my own ideas, approaches and enhancements and it became +totally different one. + +Comparing DM snapshot and fs based implementation +- the number of bytes to be copied between devices is much smaller. +- the type of filesystem must be one and only. +- the fs must be writable, no readonly fs, even for the lower original + device. so the compression fs will not be usable. but if we use + loopback mount, we may address this issue. + for instance, + mount /cdrom/squashfs.img /sq + losetup /sq/ext2.img + losetup /somewhere/cow + dmsetup "snapshot /dev/loop0 /dev/loop1 ..." +- it will be difficult (or needs more operations) to extract the + difference between the original device and COW. +- DM snapshot-merge may help a lot when users try merging. in the + fs-layer union, users will use rsync(1). + +You may want to read my old paper "Filesystems in LiveCD" +(http://aufs.sourceforge.net/aufs2/report/sq/sq.pdf). + + +Several characters/aspects/persona of aufs +---------------------------------------------------------------------- + +Aufs has several characters, aspects or persona. +1. a filesystem, callee of VFS helper +2. sub-VFS, caller of VFS helper for branches +3. a virtual filesystem which maintains persistent inode number +4. reader/writer of files on branches such like an application + +1. Callee of VFS Helper +As an ordinary linux filesystem, aufs is a callee of VFS. For instance, +unlink(2) from an application reaches sys_unlink() kernel function and +then vfs_unlink() is called. vfs_unlink() is one of VFS helper and it +calls filesystem specific unlink operation. Actually aufs implements the +unlink operation but it behaves like a redirector. + +2. Caller of VFS Helper for Branches +aufs_unlink() passes the unlink request to the branch filesystem as if +it were called from VFS. So the called unlink operation of the branch +filesystem acts as usual. As a caller of VFS helper, aufs should handle +every necessary pre/post operation for the branch filesystem. +- acquire the lock for the parent dir on a branch +- lookup in a branch +- revalidate dentry on a branch +- mnt_want_write() for a branch +- vfs_unlink() for a branch +- mnt_drop_write() for a branch +- release the lock on a branch + +3. Persistent Inode Number +One of the most important issue for a filesystem is to maintain inode +numbers. This is particularly important to support exporting a +filesystem via NFS. Aufs is a virtual filesystem which doesn't have a +backend block device for its own. But some storage is necessary to +keep and maintain the inode numbers. It may be a large space and may not +suit to keep in memory. Aufs rents some space from its first writable +branch filesystem (by default) and creates file(s) on it. These files +are created by aufs internally and removed soon (currently) keeping +opened. +Note: Because these files are removed, they are totally gone after + unmounting aufs. It means the inode numbers are not persistent + across unmount or reboot. I have a plan to make them really + persistent which will be important for aufs on NFS server. + +4. Read/Write Files Internally (copy-on-write) +Because a branch can be readonly, when you write a file on it, aufs will +"copy-up" it to the upper writable branch internally. And then write the +originally requested thing to the file. Generally kernel doesn't +open/read/write file actively. In aufs, even a single write may cause a +internal "file copy". This behaviour is very similar to cp(1) command. + +Some people may think it is better to pass such work to user space +helper, instead of doing in kernel space. Actually I am still thinking +about it. But currently I have implemented it in kernel space. diff --git a/Documentation/filesystems/aufs/design/02struct.txt b/Documentation/filesystems/aufs/design/02struct.txt new file mode 100644 index 0000000000000..942894405aec4 --- /dev/null +++ b/Documentation/filesystems/aufs/design/02struct.txt @@ -0,0 +1,258 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Basic Aufs Internal Structure + +Superblock/Inode/Dentry/File Objects +---------------------------------------------------------------------- +As like an ordinary filesystem, aufs has its own +superblock/inode/dentry/file objects. All these objects have a +dynamically allocated array and store the same kind of pointers to the +lower filesystem, branch. +For example, when you build a union with one readwrite branch and one +readonly, mounted /au, /rw and /ro respectively. +- /au = /rw + /ro +- /ro/fileA exists but /rw/fileA + +Aufs lookup operation finds /ro/fileA and gets dentry for that. These +pointers are stored in a aufs dentry. The array in aufs dentry will be, +- [0] = NULL (because /rw/fileA doesn't exist) +- [1] = /ro/fileA + +This style of an array is essentially same to the aufs +superblock/inode/dentry/file objects. + +Because aufs supports manipulating branches, ie. add/delete/change +branches dynamically, these objects has its own generation. When +branches are changed, the generation in aufs superblock is +incremented. And a generation in other object are compared when it is +accessed. When a generation in other objects are obsoleted, aufs +refreshes the internal array. + + +Superblock +---------------------------------------------------------------------- +Additionally aufs superblock has some data for policies to select one +among multiple writable branches, XIB files, pseudo-links and kobject. +See below in detail. +About the policies which supports copy-down a directory, see +wbr_policy.txt too. + + +Branch and XINO(External Inode Number Translation Table) +---------------------------------------------------------------------- +Every branch has its own xino (external inode number translation table) +file. The xino file is created and unlinked by aufs internally. When two +members of a union exist on the same filesystem, they share the single +xino file. +The struct of a xino file is simple, just a sequence of aufs inode +numbers which is indexed by the lower inode number. +In the above sample, assume the inode number of /ro/fileA is i111 and +aufs assigns the inode number i999 for fileA. Then aufs writes 999 as +4(8) bytes at 111 * 4(8) bytes offset in the xino file. + +When the inode numbers are not contiguous, the xino file will be sparse +which has a hole in it and doesn't consume as much disk space as it +might appear. If your branch filesystem consumes disk space for such +holes, then you should specify 'xino=' option at mounting aufs. + +Aufs has a mount option to free the disk blocks for such holes in XINO +files on tmpfs or ramdisk. But it is not so effective actually. If you +meet a problem of disk shortage due to XINO files, then you should try +"tmpfs-ino.patch" (and "vfs-ino.patch" too) in aufs4-standalone.git. +The patch localizes the assignment inumbers per tmpfs-mount and avoid +the holes in XINO files. + +Also a writable branch has three kinds of "whiteout bases". All these +are existed when the branch is joined to aufs, and their names are +whiteout-ed doubly, so that users will never see their names in aufs +hierarchy. +1. a regular file which will be hardlinked to all whiteouts. +2. a directory to store a pseudo-link. +3. a directory to store an "orphan"-ed file temporary. + +1. Whiteout Base + When you remove a file on a readonly branch, aufs handles it as a + logical deletion and creates a whiteout on the upper writable branch + as a hardlink of this file in order not to consume inode on the + writable branch. +2. Pseudo-link Dir + See below, Pseudo-link. +3. Step-Parent Dir + When "fileC" exists on the lower readonly branch only and it is + opened and removed with its parent dir, and then user writes + something into it, then aufs copies-up fileC to this + directory. Because there is no other dir to store fileC. After + creating a file under this dir, the file is unlinked. + +Because aufs supports manipulating branches, ie. add/delete/change +dynamically, a branch has its own id. When the branch order changes, +aufs finds the new index by searching the branch id. + + +Pseudo-link +---------------------------------------------------------------------- +Assume "fileA" exists on the lower readonly branch only and it is +hardlinked to "fileB" on the branch. When you write something to fileA, +aufs copies-up it to the upper writable branch. Additionally aufs +creates a hardlink under the Pseudo-link Directory of the writable +branch. The inode of a pseudo-link is kept in aufs super_block as a +simple list. If fileB is read after unlinking fileA, aufs returns +filedata from the pseudo-link instead of the lower readonly +branch. Because the pseudo-link is based upon the inode, to keep the +inode number by xino (see above) is essentially necessary. + +All the hardlinks under the Pseudo-link Directory of the writable branch +should be restored in a proper location later. Aufs provides a utility +to do this. The userspace helpers executed at remounting and unmounting +aufs by default. +During this utility is running, it puts aufs into the pseudo-link +maintenance mode. In this mode, only the process which began the +maintenance mode (and its child processes) is allowed to operate in +aufs. Some other processes which are not related to the pseudo-link will +be allowed to run too, but the rest have to return an error or wait +until the maintenance mode ends. If a process already acquires an inode +mutex (in VFS), it has to return an error. + + +XIB(external inode number bitmap) +---------------------------------------------------------------------- +Addition to the xino file per a branch, aufs has an external inode number +bitmap in a superblock object. It is also an internal file such like a +xino file. +It is a simple bitmap to mark whether the aufs inode number is in-use or +not. +To reduce the file I/O, aufs prepares a single memory page to cache xib. + +As well as XINO files, aufs has a feature to truncate/refresh XIB to +reduce the number of consumed disk blocks for these files. + + +Virtual or Vertical Dir, and Readdir in Userspace +---------------------------------------------------------------------- +In order to support multiple layers (branches), aufs readdir operation +constructs a virtual dir block on memory. For readdir, aufs calls +vfs_readdir() internally for each dir on branches, merges their entries +with eliminating the whiteout-ed ones, and sets it to file (dir) +object. So the file object has its entry list until it is closed. The +entry list will be updated when the file position is zero and becomes +obsoleted. This decision is made in aufs automatically. + +The dynamically allocated memory block for the name of entries has a +unit of 512 bytes (by default) and stores the names contiguously (no +padding). Another block for each entry is handled by kmem_cache too. +During building dir blocks, aufs creates hash list and judging whether +the entry is whiteouted by its upper branch or already listed. +The merged result is cached in the corresponding inode object and +maintained by a customizable life-time option. + +Some people may call it can be a security hole or invite DoS attack +since the opened and once readdir-ed dir (file object) holds its entry +list and becomes a pressure for system memory. But I'd say it is similar +to files under /proc or /sys. The virtual files in them also holds a +memory page (generally) while they are opened. When an idea to reduce +memory for them is introduced, it will be applied to aufs too. +For those who really hate this situation, I've developed readdir(3) +library which operates this merging in userspace. You just need to set +LD_PRELOAD environment variable, and aufs will not consume no memory in +kernel space for readdir(3). + + +Workqueue +---------------------------------------------------------------------- +Aufs sometimes requires privilege access to a branch. For instance, +in copy-up/down operation. When a user process is going to make changes +to a file which exists in the lower readonly branch only, and the mode +of one of ancestor directories may not be writable by a user +process. Here aufs copy-up the file with its ancestors and they may +require privilege to set its owner/group/mode/etc. +This is a typical case of a application character of aufs (see +Introduction). + +Aufs uses workqueue synchronously for this case. It creates its own +workqueue. The workqueue is a kernel thread and has privilege. Aufs +passes the request to call mkdir or write (for example), and wait for +its completion. This approach solves a problem of a signal handler +simply. +If aufs didn't adopt the workqueue and changed the privilege of the +process, then the process may receive the unexpected SIGXFSZ or other +signals. + +Also aufs uses the system global workqueue ("events" kernel thread) too +for asynchronous tasks, such like handling inotify/fsnotify, re-creating a +whiteout base and etc. This is unrelated to a privilege. +Most of aufs operation tries acquiring a rw_semaphore for aufs +superblock at the beginning, at the same time waits for the completion +of all queued asynchronous tasks. + + +Whiteout +---------------------------------------------------------------------- +The whiteout in aufs is very similar to Unionfs's. That is represented +by its filename. UnionMount takes an approach of a file mode, but I am +afraid several utilities (find(1) or something) will have to support it. + +Basically the whiteout represents "logical deletion" which stops aufs to +lookup further, but also it represents "dir is opaque" which also stop +further lookup. + +In aufs, rmdir(2) and rename(2) for dir uses whiteout alternatively. +In order to make several functions in a single systemcall to be +revertible, aufs adopts an approach to rename a directory to a temporary +unique whiteouted name. +For example, in rename(2) dir where the target dir already existed, aufs +renames the target dir to a temporary unique whiteouted name before the +actual rename on a branch, and then handles other actions (make it opaque, +update the attributes, etc). If an error happens in these actions, aufs +simply renames the whiteouted name back and returns an error. If all are +succeeded, aufs registers a function to remove the whiteouted unique +temporary name completely and asynchronously to the system global +workqueue. + + +Copy-up +---------------------------------------------------------------------- +It is a well-known feature or concept. +When user modifies a file on a readonly branch, aufs operate "copy-up" +internally and makes change to the new file on the upper writable branch. +When the trigger systemcall does not update the timestamps of the parent +dir, aufs reverts it after copy-up. + + +Move-down (aufs3.9 and later) +---------------------------------------------------------------------- +"Copy-up" is one of the essential feature in aufs. It copies a file from +the lower readonly branch to the upper writable branch when a user +changes something about the file. +"Move-down" is an opposite action of copy-up. Basically this action is +ran manually instead of automatically and internally. +For desgin and implementation, aufs has to consider these issues. +- whiteout for the file may exist on the lower branch. +- ancestor directories may not exist on the lower branch. +- diropq for the ancestor directories may exist on the upper branch. +- free space on the lower branch will reduce. +- another access to the file may happen during moving-down, including + UDBA (see "Revalidate Dentry and UDBA"). +- the file should not be hard-linked nor pseudo-linked. they should be + handled by auplink utility later. + +Sometimes users want to move-down a file from the upper writable branch +to the lower readonly or writable branch. For instance, +- the free space of the upper writable branch is going to run out. +- create a new intermediate branch between the upper and lower branch. +- etc. + +For this purpose, use "aumvdown" command in aufs-util.git. diff --git a/Documentation/filesystems/aufs/design/03atomic_open.txt b/Documentation/filesystems/aufs/design/03atomic_open.txt new file mode 100644 index 0000000000000..ec7f320767f29 --- /dev/null +++ b/Documentation/filesystems/aufs/design/03atomic_open.txt @@ -0,0 +1,85 @@ + +# Copyright (C) 2015-2016 Junjiro R. Okajima +# +# 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, see . + +Support for a branch who has its ->atomic_open() +---------------------------------------------------------------------- +The filesystems who implement its ->atomic_open() are not majority. For +example NFSv4 does, and aufs should call NFSv4 ->atomic_open, +particularly for open(O_CREAT|O_EXCL, 0400) case. Other than +->atomic_open(), NFSv4 returns an error for this open(2). While I am not +sure whether all filesystems who have ->atomic_open() behave like this, +but NFSv4 surely returns the error. + +In order to support ->atomic_open() for aufs, there are a few +approaches. + +A. Introduce aufs_atomic_open() + - calls one of VFS:do_last(), lookup_open() or atomic_open() for + branch fs. +B. Introduce aufs_atomic_open() calling create, open and chmod. this is + an aufs user Pip Cet's approach + - calls aufs_create(), VFS finish_open() and notify_change(). + - pass fake-mode to finish_open(), and then correct the mode by + notify_change(). +C. Extend aufs_open() to call branch fs's ->atomic_open() + - no aufs_atomic_open(). + - aufs_lookup() registers the TID to an aufs internal object. + - aufs_create() does nothing when the matching TID is registered, but + registers the mode. + - aufs_open() calls branch fs's ->atomic_open() when the matching + TID is registered. +D. Extend aufs_open() to re-try branch fs's ->open() with superuser's + credential + - no aufs_atomic_open(). + - aufs_create() registers the TID to an internal object. this info + represents "this process created this file just now." + - when aufs gets EACCES from branch fs's ->open(), then confirm the + registered TID and re-try open() with superuser's credential. + +Pros and cons for each approach. + +A. + - straightforward but highly depends upon VFS internal. + - the atomic behavaiour is kept. + - some of parameters such as nameidata are hard to reproduce for + branch fs. + - large overhead. +B. + - easy to implement. + - the atomic behavaiour is lost. +C. + - the atomic behavaiour is kept. + - dirty and tricky. + - VFS checks whether the file is created correctly after calling + ->create(), which means this approach doesn't work. +D. + - easy to implement. + - the atomic behavaiour is lost. + - to open a file with superuser's credential and give it to a user + process is a bad idea, since the file object keeps the credential + in it. It may affect LSM or something. This approach doesn't work + either. + +The approach A is ideal, but it hard to implement. So here is a +variation of A, which is to be implemented. + +A-1. Introduce aufs_atomic_open() + - calls branch fs ->atomic_open() if exists. otherwise calls + vfs_create() and finish_open(). + - the demerit is that the several checks after branch fs + ->atomic_open() are lost. in the ordinary case, the checks are + done by VFS:do_last(), lookup_open() and atomic_open(). some can + be implemented in aufs, but not all I am afraid. diff --git a/Documentation/filesystems/aufs/design/03lookup.txt b/Documentation/filesystems/aufs/design/03lookup.txt new file mode 100644 index 0000000000000..b57a9ebbd30c2 --- /dev/null +++ b/Documentation/filesystems/aufs/design/03lookup.txt @@ -0,0 +1,113 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Lookup in a Branch +---------------------------------------------------------------------- +Since aufs has a character of sub-VFS (see Introduction), it operates +lookup for branches as VFS does. It may be a heavy work. But almost all +lookup operation in aufs is the simplest case, ie. lookup only an entry +directly connected to its parent. Digging down the directory hierarchy +is unnecessary. VFS has a function lookup_one_len() for that use, and +aufs calls it. + +When a branch is a remote filesystem, aufs basically relies upon its +->d_revalidate(), also aufs forces the hardest revalidate tests for +them. +For d_revalidate, aufs implements three levels of revalidate tests. See +"Revalidate Dentry and UDBA" in detail. + + +Test Only the Highest One for the Directory Permission (dirperm1 option) +---------------------------------------------------------------------- +Let's try case study. +- aufs has two branches, upper readwrite and lower readonly. + /au = /rw + /ro +- "dirA" exists under /ro, but /rw. and its mode is 0700. +- user invoked "chmod a+rx /au/dirA" +- the internal copy-up is activated and "/rw/dirA" is created and its + permission bits are set to world readable. +- then "/au/dirA" becomes world readable? + +In this case, /ro/dirA is still 0700 since it exists in readonly branch, +or it may be a natively readonly filesystem. If aufs respects the lower +branch, it should not respond readdir request from other users. But user +allowed it by chmod. Should really aufs rejects showing the entries +under /ro/dirA? + +To be honest, I don't have a good solution for this case. So aufs +implements 'dirperm1' and 'nodirperm1' mount options, and leave it to +users. +When dirperm1 is specified, aufs checks only the highest one for the +directory permission, and shows the entries. Otherwise, as usual, checks +every dir existing on all branches and rejects the request. + +As a side effect, dirperm1 option improves the performance of aufs +because the number of permission check is reduced when the number of +branch is many. + + +Revalidate Dentry and UDBA (User's Direct Branch Access) +---------------------------------------------------------------------- +Generally VFS helpers re-validate a dentry as a part of lookup. +0. digging down the directory hierarchy. +1. lock the parent dir by its i_mutex. +2. lookup the final (child) entry. +3. revalidate it. +4. call the actual operation (create, unlink, etc.) +5. unlock the parent dir + +If the filesystem implements its ->d_revalidate() (step 3), then it is +called. Actually aufs implements it and checks the dentry on a branch is +still valid. +But it is not enough. Because aufs has to release the lock for the +parent dir on a branch at the end of ->lookup() (step 2) and +->d_revalidate() (step 3) while the i_mutex of the aufs dir is still +held by VFS. +If the file on a branch is changed directly, eg. bypassing aufs, after +aufs released the lock, then the subsequent operation may cause +something unpleasant result. + +This situation is a result of VFS architecture, ->lookup() and +->d_revalidate() is separated. But I never say it is wrong. It is a good +design from VFS's point of view. It is just not suitable for sub-VFS +character in aufs. + +Aufs supports such case by three level of revalidation which is +selectable by user. +1. Simple Revalidate + Addition to the native flow in VFS's, confirm the child-parent + relationship on the branch just after locking the parent dir on the + branch in the "actual operation" (step 4). When this validation + fails, aufs returns EBUSY. ->d_revalidate() (step 3) in aufs still + checks the validation of the dentry on branches. +2. Monitor Changes Internally by Inotify/Fsnotify + Addition to above, in the "actual operation" (step 4) aufs re-lookup + the dentry on the branch, and returns EBUSY if it finds different + dentry. + Additionally, aufs sets the inotify/fsnotify watch for every dir on branches + during it is in cache. When the event is notified, aufs registers a + function to kernel 'events' thread by schedule_work(). And the + function sets some special status to the cached aufs dentry and inode + private data. If they are not cached, then aufs has nothing to + do. When the same file is accessed through aufs (step 0-3) later, + aufs will detect the status and refresh all necessary data. + In this mode, aufs has to ignore the event which is fired by aufs + itself. +3. No Extra Validation + This is the simplest test and doesn't add any additional revalidation + test, and skip the revalidation in step 4. It is useful and improves + aufs performance when system surely hide the aufs branches from user, + by over-mounting something (or another method). diff --git a/Documentation/filesystems/aufs/design/04branch.txt b/Documentation/filesystems/aufs/design/04branch.txt new file mode 100644 index 0000000000000..2639ce5d151e6 --- /dev/null +++ b/Documentation/filesystems/aufs/design/04branch.txt @@ -0,0 +1,74 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Branch Manipulation + +Since aufs supports dynamic branch manipulation, ie. add/remove a branch +and changing its permission/attribute, there are a lot of works to do. + + +Add a Branch +---------------------------------------------------------------------- +o Confirm the adding dir exists outside of aufs, including loopback + mount, and its various attributes. +o Initialize the xino file and whiteout bases if necessary. + See struct.txt. + +o Check the owner/group/mode of the directory + When the owner/group/mode of the adding directory differs from the + existing branch, aufs issues a warning because it may impose a + security risk. + For example, when a upper writable branch has a world writable empty + top directory, a malicious user can create any files on the writable + branch directly, like copy-up and modify manually. If something like + /etc/{passwd,shadow} exists on the lower readonly branch but the upper + writable branch, and the writable branch is world-writable, then a + malicious guy may create /etc/passwd on the writable branch directly + and the infected file will be valid in aufs. + I am afraid it can be a security issue, but aufs can do nothing except + producing a warning. + + +Delete a Branch +---------------------------------------------------------------------- +o Confirm the deleting branch is not busy + To be general, there is one merit to adopt "remount" interface to + manipulate branches. It is to discard caches. At deleting a branch, + aufs checks the still cached (and connected) dentries and inodes. If + there are any, then they are all in-use. An inode without its + corresponding dentry can be alive alone (for example, inotify/fsnotify case). + + For the cached one, aufs checks whether the same named entry exists on + other branches. + If the cached one is a directory, because aufs provides a merged view + to users, as long as one dir is left on any branch aufs can show the + dir to users. In this case, the branch can be removed from aufs. + Otherwise aufs rejects deleting the branch. + + If any file on the deleting branch is opened by aufs, then aufs + rejects deleting. + + +Modify the Permission of a Branch +---------------------------------------------------------------------- +o Re-initialize or remove the xino file and whiteout bases if necessary. + See struct.txt. + +o rw --> ro: Confirm the modifying branch is not busy + Aufs rejects the request if any of these conditions are true. + - a file on the branch is mmap-ed. + - a regular file on the branch is opened for write and there is no + same named entry on the upper branch. diff --git a/Documentation/filesystems/aufs/design/05wbr_policy.txt b/Documentation/filesystems/aufs/design/05wbr_policy.txt new file mode 100644 index 0000000000000..a5e305fd2337e --- /dev/null +++ b/Documentation/filesystems/aufs/design/05wbr_policy.txt @@ -0,0 +1,64 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Policies to Select One among Multiple Writable Branches +---------------------------------------------------------------------- +When the number of writable branch is more than one, aufs has to decide +the target branch for file creation or copy-up. By default, the highest +writable branch which has the parent (or ancestor) dir of the target +file is chosen (top-down-parent policy). +By user's request, aufs implements some other policies to select the +writable branch, for file creation several policies, round-robin, +most-free-space, and other policies. For copy-up, top-down-parent, +bottom-up-parent, bottom-up and others. + +As expected, the round-robin policy selects the branch in circular. When +you have two writable branches and creates 10 new files, 5 files will be +created for each branch. mkdir(2) systemcall is an exception. When you +create 10 new directories, all will be created on the same branch. +And the most-free-space policy selects the one which has most free +space among the writable branches. The amount of free space will be +checked by aufs internally, and users can specify its time interval. + +The policies for copy-up is more simple, +top-down-parent is equivalent to the same named on in create policy, +bottom-up-parent selects the writable branch where the parent dir +exists and the nearest upper one from the copyup-source, +bottom-up selects the nearest upper writable branch from the +copyup-source, regardless the existence of the parent dir. + +There are some rules or exceptions to apply these policies. +- If there is a readonly branch above the policy-selected branch and + the parent dir is marked as opaque (a variation of whiteout), or the + target (creating) file is whiteout-ed on the upper readonly branch, + then the result of the policy is ignored and the target file will be + created on the nearest upper writable branch than the readonly branch. +- If there is a writable branch above the policy-selected branch and + the parent dir is marked as opaque or the target file is whiteouted + on the branch, then the result of the policy is ignored and the target + file will be created on the highest one among the upper writable + branches who has diropq or whiteout. In case of whiteout, aufs removes + it as usual. +- link(2) and rename(2) systemcalls are exceptions in every policy. + They try selecting the branch where the source exists as possible + since copyup a large file will take long time. If it can't be, + ie. the branch where the source exists is readonly, then they will + follow the copyup policy. +- There is an exception for rename(2) when the target exists. + If the rename target exists, aufs compares the index of the branches + where the source and the target exists and selects the higher + one. If the selected branch is readonly, then aufs follows the + copyup policy. diff --git a/Documentation/filesystems/aufs/design/06fhsm.txt b/Documentation/filesystems/aufs/design/06fhsm.txt new file mode 100644 index 0000000000000..4d2b61ff2661b --- /dev/null +++ b/Documentation/filesystems/aufs/design/06fhsm.txt @@ -0,0 +1,120 @@ + +# Copyright (C) 2011-2016 Junjiro R. Okajima +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +File-based Hierarchical Storage Management (FHSM) +---------------------------------------------------------------------- +Hierarchical Storage Management (or HSM) is a well-known feature in the +storage world. Aufs provides this feature as file-based with multiple +writable branches, based upon the principle of "Colder, the Lower". +Here the word "colder" means that the less used files, and "lower" means +that the position in the order of the stacked branches vertically. +These multiple writable branches are prioritized, ie. the topmost one +should be the fastest drive and be used heavily. + +o Characters in aufs FHSM story +- aufs itself and a new branch attribute. +- a new ioctl interface to move-down and to establish a connection with + the daemon ("move-down" is a converse of "copy-up"). +- userspace tool and daemon. + +The userspace daemon establishes a connection with aufs and waits for +the notification. The notified information is very similar to struct +statfs containing the number of consumed blocks and inodes. +When the consumed blocks/inodes of a branch exceeds the user-specified +upper watermark, the daemon activates its move-down process until the +consumed blocks/inodes reaches the user-specified lower watermark. + +The actual move-down is done by aufs based upon the request from +user-space since we need to maintain the inode number and the internal +pointer arrays in aufs. + +Currently aufs FHSM handles the regular files only. Additionally they +must not be hard-linked nor pseudo-linked. + + +o Cowork of aufs and the user-space daemon + During the userspace daemon established the connection, aufs sends a + small notification to it whenever aufs writes something into the + writable branch. But it may cost high since aufs issues statfs(2) + internally. So user can specify a new option to cache the + info. Actually the notification is controlled by these factors. + + the specified cache time. + + classified as "force" by aufs internally. + Until the specified time expires, aufs doesn't send the info + except the forced cases. When aufs decide forcing, the info is always + notified to userspace. + For example, the number of free inodes is generally large enough and + the shortage of it happens rarely. So aufs doesn't force the + notification when creating a new file, directory and others. This is + the typical case which aufs doesn't force. + When aufs writes the actual filedata and the files consumes any of new + blocks, the aufs forces notifying. + + +o Interfaces in aufs +- New branch attribute. + + fhsm + Specifies that the branch is managed by FHSM feature. In other word, + participant in the FHSM. + When nofhsm is set to the branch, it will not be the source/target + branch of the move-down operation. This attribute is set + independently from coo and moo attributes, and if you want full + FHSM, you should specify them as well. +- New mount option. + + fhsm_sec + Specifies a second to suppress many less important info to be + notified. +- New ioctl. + + AUFS_CTL_FHSM_FD + create a new file descriptor which userspace can read the notification + (a subset of struct statfs) from aufs. +- Module parameter 'brs' + It has to be set to 1. Otherwise the new mount option 'fhsm' will not + be set. +- mount helpers /sbin/mount.aufs and /sbin/umount.aufs + When there are two or more branches with fhsm attributes, + /sbin/mount.aufs invokes the user-space daemon and /sbin/umount.aufs + terminates it. As a result of remounting and branch-manipulation, the + number of branches with fhsm attribute can be one. In this case, + /sbin/mount.aufs will terminate the user-space daemon. + + +Finally the operation is done as these steps in kernel-space. +- make sure that, + + no one else is using the file. + + the file is not hard-linked. + + the file is not pseudo-linked. + + the file is a regular file. + + the parent dir is not opaqued. +- find the target writable branch. +- make sure the file is not whiteout-ed by the upper (than the target) + branch. +- make the parent dir on the target branch. +- mutex lock the inode on the branch. +- unlink the whiteout on the target branch (if exists). +- lookup and create the whiteout-ed temporary name on the target branch. +- copy the file as the whiteout-ed temporary name on the target branch. +- rename the whiteout-ed temporary name to the original name. +- unlink the file on the source branch. +- maintain the internal pointer array and the external inode number + table (XINO). +- maintain the timestamps and other attributes of the parent dir and the + file. + +And of course, in every step, an error may happen. So the operation +should restore the original file state after an error happens. diff --git a/Documentation/filesystems/aufs/design/06mmap.txt b/Documentation/filesystems/aufs/design/06mmap.txt new file mode 100644 index 0000000000000..7e65ada931adf --- /dev/null +++ b/Documentation/filesystems/aufs/design/06mmap.txt @@ -0,0 +1,72 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +mmap(2) -- File Memory Mapping +---------------------------------------------------------------------- +In aufs, the file-mapped pages are handled by a branch fs directly, no +interaction with aufs. It means aufs_mmap() calls the branch fs's +->mmap(). +This approach is simple and good, but there is one problem. +Under /proc, several entries show the mmapped files by its path (with +device and inode number), and the printed path will be the path on the +branch fs's instead of virtual aufs's. +This is not a problem in most cases, but some utilities lsof(1) (and its +user) may expect the path on aufs. + +To address this issue, aufs adds a new member called vm_prfile in struct +vm_area_struct (and struct vm_region). The original vm_file points to +the file on the branch fs in order to handle everything correctly as +usual. The new vm_prfile points to a virtual file in aufs, and the +show-functions in procfs refers to vm_prfile if it is set. +Also we need to maintain several other places where touching vm_file +such like +- fork()/clone() copies vma and the reference count of vm_file is + incremented. +- merging vma maintains the ref count too. + +This is not a good approach. It just fakes the printed path. But it +leaves all behaviour around f_mapping unchanged. This is surely an +advantage. +Actually aufs had adopted another complicated approach which calls +generic_file_mmap() and handles struct vm_operations_struct. In this +approach, aufs met a hard problem and I could not solve it without +switching the approach. + +There may be one more another approach which is +- bind-mount the branch-root onto the aufs-root internally +- grab the new vfsmount (ie. struct mount) +- lazy-umount the branch-root internally +- in open(2) the aufs-file, open the branch-file with the hidden + vfsmount (instead of the original branch's vfsmount) +- ideally this "bind-mount and lazy-umount" should be done atomically, + but it may be possible from userspace by the mount helper. + +Adding the internal hidden vfsmount and using it in opening a file, the +file path under /proc will be printed correctly. This approach looks +smarter, but is not possible I am afraid. +- aufs-root may be bind-mount later. when it happens, another hidden + vfsmount will be required. +- it is hard to get the chance to bind-mount and lazy-umount + + in kernel-space, FS can have vfsmount in open(2) via + file->f_path, and aufs can know its vfsmount. But several locks are + already acquired, and if aufs tries to bind-mount and lazy-umount + here, then it may cause a deadlock. + + in user-space, bind-mount doesn't invoke the mount helper. +- since /proc shows dev and ino, aufs has to give vma these info. it + means a new member vm_prinode will be necessary. this is essentially + equivalent to vm_prfile described above. + +I have to give up this "looks-smater" approach. diff --git a/Documentation/filesystems/aufs/design/06xattr.txt b/Documentation/filesystems/aufs/design/06xattr.txt new file mode 100644 index 0000000000000..4242fd8c9afc8 --- /dev/null +++ b/Documentation/filesystems/aufs/design/06xattr.txt @@ -0,0 +1,96 @@ + +# Copyright (C) 2014-2016 Junjiro R. Okajima +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Listing XATTR/EA and getting the value +---------------------------------------------------------------------- +For the inode standard attributes (owner, group, timestamps, etc.), aufs +shows the values from the topmost existing file. This behaviour is good +for the non-dir entries since the bahaviour exactly matches the shown +information. But for the directories, aufs considers all the same named +entries on the lower branches. Which means, if one of the lower entry +rejects readdir call, then aufs returns an error even if the topmost +entry allows it. This behaviour is necessary to respect the branch fs's +security, but can make users confused since the user-visible standard +attributes don't match the behaviour. +To address this issue, aufs has a mount option called dirperm1 which +checks the permission for the topmost entry only, and ignores the lower +entry's permission. + +A similar issue can happen around XATTR. +getxattr(2) and listxattr(2) families behave as if dirperm1 option is +always set. Otherwise these very unpleasant situation would happen. +- listxattr(2) may return the duplicated entries. +- users may not be able to remove or reset the XATTR forever, + + +XATTR/EA support in the internal (copy,move)-(up,down) +---------------------------------------------------------------------- +Generally the extended attributes of inode are categorized as these. +- "security" for LSM and capability. +- "system" for posix ACL, 'acl' mount option is required for the branch + fs generally. +- "trusted" for userspace, CAP_SYS_ADMIN is required. +- "user" for userspace, 'user_xattr' mount option is required for the + branch fs generally. + +Moreover there are some other categories. Aufs handles these rather +unpopular categories as the ordinary ones, ie. there is no special +condition nor exception. + +In copy-up, the support for XATTR on the dst branch may differ from the +src branch. In this case, the copy-up operation will get an error and +the original user operation which triggered the copy-up will fail. It +can happen that even all copy-up will fail. +When both of src and dst branches support XATTR and if an error occurs +during copying XATTR, then the copy-up should fail obviously. That is a +good reason and aufs should return an error to userspace. But when only +the src branch support that XATTR, aufs should not return an error. +For example, the src branch supports ACL but the dst branch doesn't +because the dst branch may natively un-support it or temporary +un-support it due to "noacl" mount option. Of course, the dst branch fs +may NOT return an error even if the XATTR is not supported. It is +totally up to the branch fs. + +Anyway when the aufs internal copy-up gets an error from the dst branch +fs, then aufs tries removing the just copied entry and returns the error +to the userspace. The worst case of this situation will be all copy-up +will fail. + +For the copy-up operation, there two basic approaches. +- copy the specified XATTR only (by category above), and return the + error unconditionally if it happens. +- copy all XATTR, and ignore the error on the specified category only. + +In order to support XATTR and to implement the correct behaviour, aufs +chooses the latter approach and introduces some new branch attributes, +"icexsec", "icexsys", "icextr", "icexusr", and "icexoth". +They correspond to the XATTR namespaces (see above). Additionally, to be +convenient, "icex" is also provided which means all "icex*" attributes +are set (here the word "icex" stands for "ignore copy-error on XATTR"). + +The meaning of these attributes is to ignore the error from setting +XATTR on that branch. +Note that aufs tries copying all XATTR unconditionally, and ignores the +error from the dst branch according to the specified attributes. + +Some XATTR may have its default value. The default value may come from +the parent dir or the environment. If the default value is set at the +file creating-time, it will be overwritten by copy-up. +Some contradiction may happen I am afraid. +Do we need another attribute to stop copying XATTR? I am unsure. For +now, aufs implements the branch attributes to ignore the error. diff --git a/Documentation/filesystems/aufs/design/07export.txt b/Documentation/filesystems/aufs/design/07export.txt new file mode 100644 index 0000000000000..516f168764d53 --- /dev/null +++ b/Documentation/filesystems/aufs/design/07export.txt @@ -0,0 +1,58 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Export Aufs via NFS +---------------------------------------------------------------------- +Here is an approach. +- like xino/xib, add a new file 'xigen' which stores aufs inode + generation. +- iget_locked(): initialize aufs inode generation for a new inode, and + store it in xigen file. +- destroy_inode(): increment aufs inode generation and store it in xigen + file. it is necessary even if it is not unlinked, because any data of + inode may be changed by UDBA. +- encode_fh(): for a root dir, simply return FILEID_ROOT. otherwise + build file handle by + + branch id (4 bytes) + + superblock generation (4 bytes) + + inode number (4 or 8 bytes) + + parent dir inode number (4 or 8 bytes) + + inode generation (4 bytes)) + + return value of exportfs_encode_fh() for the parent on a branch (4 + bytes) + + file handle for a branch (by exportfs_encode_fh()) +- fh_to_dentry(): + + find the index of a branch from its id in handle, and check it is + still exist in aufs. + + 1st level: get the inode number from handle and search it in cache. + + 2nd level: if not found in cache, get the parent inode number from + the handle and search it in cache. and then open the found parent + dir, find the matching inode number by vfs_readdir() and get its + name, and call lookup_one_len() for the target dentry. + + 3rd level: if the parent dir is not cached, call + exportfs_decode_fh() for a branch and get the parent on a branch, + build a pathname of it, convert it a pathname in aufs, call + path_lookup(). now aufs gets a parent dir dentry, then handle it as + the 2nd level. + + to open the dir, aufs needs struct vfsmount. aufs keeps vfsmount + for every branch, but not itself. to get this, (currently) aufs + searches in current->nsproxy->mnt_ns list. it may not be a good + idea, but I didn't get other approach. + + test the generation of the gotten inode. +- every inode operation: they may get EBUSY due to UDBA. in this case, + convert it into ESTALE for NFSD. +- readdir(): call lockdep_on/off() because filldir in NFSD calls + lookup_one_len(), vfs_getattr(), encode_fh() and others. diff --git a/Documentation/filesystems/aufs/design/08shwh.txt b/Documentation/filesystems/aufs/design/08shwh.txt new file mode 100644 index 0000000000000..cd3e56d9f222b --- /dev/null +++ b/Documentation/filesystems/aufs/design/08shwh.txt @@ -0,0 +1,52 @@ + +# Copyright (C) 2005-2016 Junjiro R. Okajima +# +# 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, see . + +Show Whiteout Mode (shwh) +---------------------------------------------------------------------- +Generally aufs hides the name of whiteouts. But in some cases, to show +them is very useful for users. For instance, creating a new middle layer +(branch) by merging existing layers. + +(borrowing aufs1 HOW-TO from a user, Michael Towers) +When you have three branches, +- Bottom: 'system', squashfs (underlying base system), read-only +- Middle: 'mods', squashfs, read-only +- Top: 'overlay', ram (tmpfs), read-write + +The top layer is loaded at boot time and saved at shutdown, to preserve +the changes made to the system during the session. +When larger changes have been made, or smaller changes have accumulated, +the size of the saved top layer data grows. At this point, it would be +nice to be able to merge the two overlay branches ('mods' and 'overlay') +and rewrite the 'mods' squashfs, clearing the top layer and thus +restoring save and load speed. + +This merging is simplified by the use of another aufs mount, of just the +two overlay branches using the 'shwh' option. +# mount -t aufs -o ro,shwh,br:/livesys/overlay=ro+wh:/livesys/mods=rr+wh \ + aufs /livesys/merge_union + +A merged view of these two branches is then available at +/livesys/merge_union, and the new feature is that the whiteouts are +visible! +Note that in 'shwh' mode the aufs mount must be 'ro', which will disable +writing to all branches. Also the default mode for all branches is 'ro'. +It is now possible to save the combined contents of the two overlay +branches to a new squashfs, e.g.: +# mksquashfs /livesys/merge_union /path/to/newmods.squash + +This new squashfs archive can be stored on the boot device and the +initramfs will use it to replace the old one at the next boot. diff --git a/Documentation/filesystems/aufs/design/10dynop.txt b/Documentation/filesystems/aufs/design/10dynop.txt new file mode 100644 index 0000000000000..4ab46ffc271c0 --- /dev/null +++ b/Documentation/filesystems/aufs/design/10dynop.txt @@ -0,0 +1,47 @@ + +# Copyright (C) 2010-2016 Junjiro R. Okajima +# +# 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, see . + +Dynamically customizable FS operations +---------------------------------------------------------------------- +Generally FS operations (struct inode_operations, struct +address_space_operations, struct file_operations, etc.) are defined as +"static const", but it never means that FS have only one set of +operation. Some FS have multiple sets of them. For instance, ext2 has +three sets, one for XIP, for NOBH, and for normal. +Since aufs overrides and redirects these operations, sometimes aufs has +to change its behaviour according to the branch FS type. More importantly +VFS acts differently if a function (member in the struct) is set or +not. It means aufs should have several sets of operations and select one +among them according to the branch FS definition. + +In order to solve this problem and not to affect the behaviour of VFS, +aufs defines these operations dynamically. For instance, aufs defines +dummy direct_IO function for struct address_space_operations, but it may +not be set to the address_space_operations actually. When the branch FS +doesn't have it, aufs doesn't set it to its address_space_operations +while the function definition itself is still alive. So the behaviour +itself will not change, and it will return an error when direct_IO is +not set. + +The lifetime of these dynamically generated operation object is +maintained by aufs branch object. When the branch is removed from aufs, +the reference counter of the object is decremented. When it reaches +zero, the dynamically generated operation object will be freed. + +This approach is designed to support AIO (io_submit), Direct I/O and +XIP (DAX) mainly. +Currently this approach is applied to address_space_operations for +regular files only. diff --git a/Documentation/filesystems/configfs/configfs.txt b/Documentation/filesystems/configfs/configfs.txt index b40fec9d3f53b..eb4204cfceddc 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 @@ -166,6 +178,12 @@ among other things. For that, it needs a type. ssize_t (*store_attribute)(struct config_item *, struct configfs_attribute *, const char *, size_t); + ssize_t (*read_bin_attribute)(struct config_item *, + struct configfs_bin_attribute *, + void *, size_t); + ssize_t (*write_bin_attribute)(struct config_item *, + struct configfs_bin_attribute *, + const void *, size_t); int (*allow_link)(struct config_item *src, struct config_item *target); int (*drop_link)(struct config_item *src, @@ -177,15 +195,18 @@ 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 operations can be performed on a config_item. All items that have been allocated dynamically will need to provide the ct_item_ops->release() method. This method is called when the config_item's reference count -reaches zero. Items that wish to display an attribute need to provide -the ct_item_ops->show_attribute() method. Similarly, storing a new -attribute value uses the store_attribute() method. +reaches zero. Items that wish to display an normal attribute need to provide +the ct_item_ops->show_attribute() method, while binary attributes provide the +ct_item_ops->read_bin_attribute(). Similarly, storing a new normal attribute +value uses the store_attribute() method, while the binarys' attribute equivalent +is the ct_item_ops->write_bin_attribute() method. [struct configfs_attribute] @@ -207,6 +228,32 @@ ct_item_ops->show_attribute() method, that method will be called whenever userspace asks for a read(2) on the attribute. The converse will happen for write(2). +[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/misc-devices/bone_capemgr.txt b/Documentation/misc-devices/bone_capemgr.txt new file mode 100644 index 0000000000000..2a8c766b4898c --- /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/nvmem/nvmem.txt b/Documentation/nvmem/nvmem.txt new file mode 100644 index 0000000000000..dbd40d8792394 --- /dev/null +++ b/Documentation/nvmem/nvmem.txt @@ -0,0 +1,152 @@ + NVMEM SUBSYSTEM + Srinivas Kandagatla + +This document explains the NVMEM Framework along with the APIs provided, +and how to use it. + +1. Introduction +=============== +*NVMEM* is the abbreviation for Non Volatile Memory layer. It is used to +retrieve configuration of SOC or Device specific data from non volatile +memories like eeprom, efuses and so on. + +Before this framework existed, NVMEM drivers like eeprom were stored in +drivers/misc, where they all had to duplicate pretty much the same code to +register a sysfs file, allow in-kernel users to access the content of the +devices they were driving, etc. + +This was also a problem as far as other in-kernel users were involved, since +the solutions used were pretty much different from one driver to another, there +was a rather big abstraction leak. + +This framework aims at solve these problems. It also introduces DT +representation for consumer devices to go get the data they require (MAC +Addresses, SoC/Revision ID, part numbers, and so on) from the NVMEMs. This +framework is based on regmap, so that most of the abstraction available in +regmap can be reused, across multiple types of buses. + +NVMEM Providers ++++++++++++++++ + +NVMEM provider refers to an entity that implements methods to initialize, read +and write the non-volatile memory. + +2. Registering/Unregistering the NVMEM provider +=============================================== + +A NVMEM provider can register with NVMEM core by supplying relevant +nvmem configuration to nvmem_register(), on success core would return a valid +nvmem_device pointer. + +nvmem_unregister(nvmem) is used to unregister a previously registered provider. + +For example, a simple qfprom case: + +static struct nvmem_config econfig = { + .name = "qfprom", + .owner = THIS_MODULE, +}; + +static int qfprom_probe(struct platform_device *pdev) +{ + ... + econfig.dev = &pdev->dev; + nvmem = nvmem_register(&econfig); + ... +} + +It is mandatory that the NVMEM provider has a regmap associated with its +struct device. Failure to do would return error code from nvmem_register(). + +NVMEM Consumers ++++++++++++++++ + +NVMEM consumers are the entities which make use of the NVMEM provider to +read from and to NVMEM. + +3. NVMEM cell based consumer APIs +================================= + +NVMEM cells are the data entries/fields in the NVMEM. +The NVMEM framework provides 3 APIs to read/write NVMEM cells. + +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *name); +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *name); + +void nvmem_cell_put(struct nvmem_cell *cell); +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell); + +void *nvmem_cell_read(struct nvmem_cell *cell, ssize_t *len); +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, ssize_t len); + +*nvmem_cell_get() apis will get a reference to nvmem cell for a given id, +and nvmem_cell_read/write() can then read or write to the cell. +Once the usage of the cell is finished the consumer should call *nvmem_cell_put() +to free all the allocation memory for the cell. + +4. Direct NVMEM device based consumer APIs +========================================== + +In some instances it is necessary to directly read/write the NVMEM. +To facilitate such consumers NVMEM framework provides below apis. + +struct nvmem_device *nvmem_device_get(struct device *dev, const char *name); +struct nvmem_device *devm_nvmem_device_get(struct device *dev, + const char *name); +void nvmem_device_put(struct nvmem_device *nvmem); +int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); + +Before the consumers can read/write NVMEM directly, it should get hold +of nvmem_controller from one of the *nvmem_device_get() api. + +The difference between these apis and cell based apis is that these apis always +take nvmem_device as parameter. + +5. Releasing a reference to the NVMEM +===================================== + +When a consumers no longer needs the NVMEM, it has to release the reference +to the NVMEM it has obtained using the APIs mentioned in the above section. +The NVMEM framework provides 2 APIs to release a reference to the NVMEM. + +void nvmem_cell_put(struct nvmem_cell *cell); +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell); +void nvmem_device_put(struct nvmem_device *nvmem); +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem); + +Both these APIs are used to release a reference to the NVMEM and +devm_nvmem_cell_put and devm_nvmem_device_put destroys the devres associated +with this NVMEM. + +Userspace ++++++++++ + +6. Userspace binary interface +============================== + +Userspace can read/write the raw NVMEM file located at +/sys/bus/nvmem/devices/*/nvmem + +ex: + +hexdump /sys/bus/nvmem/devices/qfprom0/nvmem + +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +00000a0 db10 2240 0000 e000 0c00 0c00 0000 0c00 +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +... +* +0001000 + +7. DeviceTree Binding +===================== + +See Documentation/devicetree/bindings/nvmem/nvmem.txt diff --git a/Documentation/printk-formats.txt b/Documentation/printk-formats.txt index 2216eb187c213..a54f44ff99e8e 100644 --- a/Documentation/printk-formats.txt +++ b/Documentation/printk-formats.txt @@ -284,6 +284,37 @@ bitmap and its derivatives such as cpumask and nodemask: 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 + Thank you for your cooperation and attention. diff --git a/MAINTAINERS b/MAINTAINERS index 09c62acb7267d..bb00fd17fadfb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1880,6 +1880,19 @@ F: include/linux/audit.h F: include/uapi/linux/audit.h F: kernel/audit* +AUFS (advanced multi layered unification filesystem) FILESYSTEM +M: "J. R. Okajima" +L: linux-unionfs@vger.kernel.org +L: aufs-users@lists.sourceforge.net (members only) +W: http://aufs.sourceforge.net +T: git://github.com/sfjro/aufs4-linux.git +S: Supported +F: Documentation/filesystems/aufs/ +F: Documentation/ABI/testing/debugfs-aufs +F: Documentation/ABI/testing/sysfs-aufs +F: fs/aufs/ +F: include/uapi/linux/aufs_type.h + AUXILIARY DISPLAY DRIVERS M: Miguel Ojeda Sandonis W: http://miguelojeda.es/auxdisplay.htm @@ -1973,6 +1986,14 @@ W: http://bcache.evilpiepirate.org S: Maintained: F: drivers/md/bcache/ +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 diff --git a/arch/arm/boot/Makefile b/arch/arm/boot/Makefile index 9eca7aee927f3..d4273d4df0b77 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 4798eb44fd267..2e3fb144ae1a4 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 \ @@ -410,10 +416,32 @@ dtb-$(CONFIG_SOC_AM33XX) += \ am335x-base0033.dtb \ am335x-bone.dtb \ am335x-boneblack.dtb \ + am335x-bonegreen-ctag-face.dtb \ + am335x-boneblack-ctag-face.dtb \ + am335x-sancloud-bbe.dtb \ + am335x-boneenhanced.dtb \ + am335x-bonegreen-overlay.dtb \ + am335x-bonegreen-wireless.dtb \ + am335x-bonegreen.dtb \ + am335x-boneblack-audio.dtb \ + am335x-boneblack-wl1835mod.dtb \ + am335x-boneblack-ttyS2.dtb \ + am335x-boneblack-spi0.dtb \ + am335x-boneblack-replicape.dtb \ + am335x-boneblack-bbbmini.dtb \ + am335x-boneblack-overlay.dtb \ + am335x-boneblack-nhdmi-overlay.dtb \ + am335x-boneblack-hdmi-overlay.dtb \ + am335x-boneblack-emmc-overlay.dtb \ + am335x-boneblack-bbb-exp-r.dtb \ + am335x-boneblack-bbb-exp-c.dtb \ + am335x-arduino-tre.dtb \ + am335x-abbbi.dtb \ am335x-evm.dtb \ am335x-evmsk.dtb \ am335x-icev2.dtb \ am335x-nano.dtb \ + am335x-olimex-som.dtb \ am335x-pepper.dtb \ am335x-lxm.dtb \ am335x-chiliboard.dtb diff --git a/arch/arm/boot/dts/am335x-abbbi.dts b/arch/arm/boot/dts/am335x-abbbi.dts new file mode 100644 index 0000000000000..5ca4e4ea96eec --- /dev/null +++ b/arch/arm/boot/dts/am335x-abbbi.dts @@ -0,0 +1,162 @@ +/* + * 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 "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"; +}; + +&sgx { + status = "okay"; +}; + +&am33xx_pinmux { + adi_hdmi_bbbi_pins: adi_hdmi_bbbi_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 */ + >; + }; + adi_hdmi_bbbi_off_pins: adi_hdmi_bbbi_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; + + 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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-arduino-tre.dts b/arch/arm/boot/dts/am335x-arduino-tre.dts new file mode 100644 index 0000000000000..7fa0849f57d3f --- /dev/null +++ b/arch/arm/boot/dts/am335x-arduino-tre.dts @@ -0,0 +1,570 @@ +/* + * 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" + +/ { + 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 { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <&userled_pins>; + + led0 { + label = "arduino_tre:yel:usr0"; + gpios = <&gpio1 21 0>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + + led1 { + label = "arduino_tre:red:usr1"; + gpios = <&gpio1 22 0>; + linux,default-trigger = "none"; + default-state = "off"; + }; + + led2 { + label = "arduino_tre:blu:usr2"; + gpios = <&gpio1 23 0>; + linux,default-trigger = "none"; + default-state = "off"; + }; + + led3 { + 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"; +}; + +&sgx { + 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"; + }; +}; + +#include "tps65217.dtsi" + +&tps { + ti,pmic-shutdown-controller; + + interrupt-parent = <&intc>; + interrupts = <7>; /* NNMI */ + + regulators { + dcdc1_reg: regulator@0 { + regulator-name = "vdd_ddr"; + regulator-min-microvolt = <1350000>; + regulator-max-microvolt = <1350000>; + regulator-boot-on; + regulator-always-on; + }; + + dcdc2_reg: regulator@1 { + /* VDD_MPU voltage limits 0.925V - 1.325V 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.925V - 1.15V 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-always-on; + }; + + ldo2_reg: regulator@4 { + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + ldo4_reg: regulator@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 a/arch/arm/boot/dts/am335x-bone-argus.dtsi b/arch/arm/boot/dts/am335x-bone-argus.dtsi new file mode 100644 index 0000000000000..0bbe0507ef0ea --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-argus.dtsi @@ -0,0 +1,85 @@ +/* + * 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. + */ +/ { + 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 */ + >; + }; +}; diff --git a/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 0000000000000..fde3d40f2134a --- /dev/null +++ b/arch/arm/boot/dts/am335x-bone-common-no-capemgr.dtsi @@ -0,0 +1,368 @@ +/* + * 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 = < + 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 */ + >; + }; + + 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 */ + >; + }; + + 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 */ + >; + }; + + 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 */ + >; + }; + + 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 */ + 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 */ + 0x108 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE7) + 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) + >; + }; + + 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 */ + 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) + 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 */ + >; + }; +}; + +&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.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 { + /* 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", "sleep"; + pinctrl-0 = <&mmc1_pins_default>; + pinctrl-1 = <&mmc1_pins_sleep>; + cd-gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>; + cd-inverted; +}; + +&aes { + status = "okay"; +}; + +&sham { + status = "okay"; +}; + +&wkup_m3_ipc { + ti,scale-data-fw = "am335x-bone-scale-data.bin"; +}; + +&rtc { + system-power-controller; +}; diff --git a/arch/arm/boot/dts/am335x-bone-common-universal.dtsi b/arch/arm/boot/dts/am335x-bone-common-universal.dtsi new file mode 100644 index 0000000000000..781e33fe3d269 --- /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 825e575650afc..5b6943bdbf61b 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,9 +62,6 @@ }; &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 */ @@ -81,22 +78,25 @@ >; }; - uart0_pins: pinmux_uart0_pins { + i2c2_pins: pinmux_i2c2_pins { pinctrl-single,pins = < - 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ - 0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ + 0x178 (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ + 0x17c (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ >; }; - clkout2_pin: pinmux_clkout2_pin { + uart0_pins: pinmux_uart0_pins { pinctrl-single,pins = < - 0x1b4 (PIN_OUTPUT_PULLDOWN | MUX_MODE3) /* xdma_event_intr1.clkout2 */ + 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ + 0x174 (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 */ 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 */ @@ -116,6 +116,8 @@ cpsw_sleep: cpsw_sleep { pinctrl-single,pins = < /* Slave 1 reset value */ + 0x108 (PIN_INPUT_PULLDOWN | MUX_MODE7) + 0x10c (PIN_INPUT_PULLDOWN | MUX_MODE7) 0x110 (PIN_INPUT_PULLDOWN | MUX_MODE7) 0x114 (PIN_INPUT_PULLDOWN | MUX_MODE7) 0x118 (PIN_INPUT_PULLDOWN | MUX_MODE7) @@ -156,7 +158,6 @@ 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 */ >; }; @@ -169,7 +170,6 @@ 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) >; }; @@ -238,8 +238,67 @@ 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>; + }; + }; }; +&i2c2 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c2_pins>; + + status = "okay"; + clock-frequency = <100000>; + + cape_eeprom0: cape_eeprom0@54 { + compatible = "at,24c256"; + reg = <0x54>; + #address-cells = <1>; + #size-cells = <1>; + cape0_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom1: cape_eeprom1@55 { + compatible = "at,24c256"; + reg = <0x55>; + #address-cells = <1>; + #size-cells = <1>; + cape1_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom2: cape_eeprom2@56 { + compatible = "at,24c256"; + reg = <0x56>; + #address-cells = <1>; + #size-cells = <1>; + cape2_data: cape_data@0 { + reg = <0 0x100>; + }; + }; + + cape_eeprom3: cape_eeprom3@57 { + compatible = "at,24c256"; + reg = <0x57>; + #address-cells = <1>; + #size-cells = <1>; + cape3_data: cape_data@0 { + reg = <0 0x100>; + }; + }; +}; + + /include/ "tps65217.dtsi" &tps { @@ -262,6 +321,9 @@ */ ti,pmic-shutdown-controller; + interrupt-parent = <&intc>; + interrupts = <7>; /* NNMI */ + regulators { dcdc1_reg: regulator@0 { regulator-name = "vdds_dpr"; @@ -313,15 +375,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"; }; @@ -357,3 +415,28 @@ &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 a/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 0000000000000..7d8f673229c0e --- /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 a/arch/arm/boot/dts/am335x-bone-jtag.dtsi b/arch/arm/boot/dts/am335x-bone-jtag.dtsi new file mode 100644 index 0000000000000..a92db8e834abf --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-can0.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-can0.dtsi new file mode 100644 index 0000000000000..09612160b10d4 --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-emmc.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-emmc.dtsi new file mode 100644 index 0000000000000..22cf4621b3572 --- /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 a/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 0000000000000..65e5fbb0cfcc1 --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-ttyS1.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS1.dtsi new file mode 100644 index 0000000000000..ae5b813f007ab --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-ttyS2.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS2.dtsi new file mode 100644 index 0000000000000..5fa593ac31a55 --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-ttyS4.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS4.dtsi new file mode 100644 index 0000000000000..1d22a95a23a23 --- /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 a/arch/arm/boot/dts/am335x-bone-pinmux-ttyS5.dtsi b/arch/arm/boot/dts/am335x-bone-pinmux-ttyS5.dtsi new file mode 100644 index 0000000000000..01d0aec1de447 --- /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 ce1b68a763637..b5d3815159dd2 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 a/arch/arm/boot/dts/am335x-boneblack-audio.dts b/arch/arm/boot/dts/am335x-boneblack-audio.dts new file mode 100644 index 0000000000000..cac3626dd611e --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-audio.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 "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 a/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 0000000000000..d8ac5a95550ff --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-c.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 "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>; +}; + +&sgx { + status = "okay"; +}; + +#include "am335x-peripheral-emmc.dtsi" +#include "am335x-bone-pinmux-emmc.dtsi" +/* #include "am335x-bone-emmc-in-reset.dtsi" */ + +/* #include "am335x-peripheral-nxp-hdmi.dtsi" */ +/* #include "am335x-bone-pinmux-nxp-hdmi.dtsi" */ + +#include "am335x-cape-bbb-exp-c.dtsi" diff --git a/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 0000000000000..7364630f3eee0 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-bbb-exp-r.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 "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>; +}; + +&sgx { + status = "okay"; +}; + +#include "am335x-peripheral-emmc.dtsi" +#include "am335x-bone-pinmux-emmc.dtsi" +/* #include "am335x-bone-emmc-in-reset.dtsi" */ + +/* #include "am335x-peripheral-nxp-hdmi.dtsi" */ +/* #include "am335x-bone-pinmux-nxp-hdmi.dtsi" */ + +#include "am335x-cape-bbb-exp-r.dtsi" diff --git a/arch/arm/boot/dts/am335x-boneblack-bbbmini.dts b/arch/arm/boot/dts/am335x-boneblack-bbbmini.dts new file mode 100644 index 0000000000000..378278baf8d4a --- /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 "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.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 a/arch/arm/boot/dts/am335x-boneblack-ctag-face.dts b/arch/arm/boot/dts/am335x-boneblack-ctag-face.dts new file mode 100644 index 0000000000000..88510e496f7cd --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-ctag-face.dts @@ -0,0 +1,99 @@ +/* + * Base device tree for BeagleBone Green with CTAG face2|4 Audio Card + * + * Author: Henrik Langer + * based on + BeagleBone Black and BeagleBone Green device tree + * + * 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.dtsi" + +/ { + model = "TI AM335x BeagleBone Black AudioCard"; + 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"; +}; + +&sgx { + status = "okay"; +}; + +&am33xx_pinmux { + mcasp0_pins: mcasp0_pins { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_ahclkx */ + 0x19c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mcasp0_axr2 */ + 0x194 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsx */ + 0x190 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx */ + 0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr */ + 0x078 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* mcasp0_aclkr */ + 0x198 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr0 */ + 0x06c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpio1[27] (enable oscillator) */ + >; + }; + + mcasp0_pins_sleep: mcasp0_pins_sleep { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_ahclkx */ + 0x19c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr2 */ + 0x194 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_fsx */ + 0x190 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_aclkx */ + 0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr */ + 0x078 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* mcasp0_aclkr */ + 0x198 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr0 */ + 0x06c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1[27] */ + >; + }; +}; + +&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 */ + 2 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 */ + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.dts new file mode 100644 index 0000000000000..d3adcaa366acf --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-emmc-overlay.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 "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"; +}; + +&sgx { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts new file mode 100644 index 0000000000000..f0e3ed2ba4f3a --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-hdmi-overlay.dts @@ -0,0 +1,163 @@ +/* + * 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.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>; +}; + +&sgx { + status = "okay"; +}; + +/* 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 = < + 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 */ + >; + }; + + 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 { + 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>; + }; + }; + }; +}; + +&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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts new file mode 100644 index 0000000000000..61dbccd80d7a7 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-nhdmi-overlay.dts @@ -0,0 +1,98 @@ +/* + * 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.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>; +}; + +&sgx { + status = "okay"; +}; + +/* 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 = < + 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 */ + >; + }; +}; + +&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 a/arch/arm/boot/dts/am335x-boneblack-overlay.dts b/arch/arm/boot/dts/am335x-boneblack-overlay.dts new file mode 100644 index 0000000000000..ba86fadbc5844 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-overlay.dts @@ -0,0 +1,41 @@ +/* + * 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.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>; +}; + +&sgx { + status = "okay"; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-replicape.dts b/arch/arm/boot/dts/am335x-boneblack-replicape.dts new file mode 100644 index 0000000000000..c03d68f47a41a --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-replicape.dts @@ -0,0 +1,87 @@ +/* + * 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.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"; +}; + +&sgx { + status = "okay"; +}; + +&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 */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; +}; + +&lcdc { + status = "okay"; +}; + +/ { + 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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-spi0.dts b/arch/arm/boot/dts/am335x-boneblack-spi0.dts new file mode 100644 index 0000000000000..00f1ea0b23763 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-spi0.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-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"; +}; + +&sgx { + status = "okay"; +}; + +&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 */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; + + 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 */ + >; + }; + + bb_spi0_pins: pinmux_bb_spi0_pins { + pinctrl-single,pins = < + 0x150 0x30 /* spi0_sclk.spi0_sclk, INPUT_PULLUP | MODE0 */ + 0x154 0x30 /* spi0_d0.spi0_d0, INPUT_PULLUP | MODE0 */ + 0x158 0x10 /* spi0_d1.spi0_d1, OUTPUT_PULLUP | MODE0 */ + 0x15c 0x10 /* spi0_cs0.spi0_cs0, OUTPUT_PULLUP | MODE0 */ + >; + }; +}; + +&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>; + }; + }; + }; +}; + +&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>; +}; + +&spi0 { + pinctrl-names = "default"; + pinctrl-0 = <&bb_spi0_pins>; + status = "okay"; + + channel@0 { + compatible = "spidev"; + spi-max-frequency = <16000000>; + reg = <0>; + spi-cpha; + }; + + channel@1 { + compatible = "spidev"; + spi-max-frequency = <16000000>; + reg = <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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-ttyS2.dts b/arch/arm/boot/dts/am335x-boneblack-ttyS2.dts new file mode 100644 index 0000000000000..3064704bcc22c --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-ttyS2.dts @@ -0,0 +1,177 @@ +/* + * 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.dtsi" +#include "am33xx-overlay-edma-fix.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"; +}; + +&sgx { + status = "okay"; +}; + +&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 */ + >; + }; + nxp_hdmi_bonelt_off_pins: nxp_hdmi_bonelt_off_pins { + pinctrl-single,pins = < + 0x1b0 0x03 /* xdma_event_intr0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT */ + >; + }; + + 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 */ + >; + }; + + bb_uart2_pins: pinmux_bb_uart2_pins { + pinctrl-single,pins = < + BONE_P9_21 (PIN_OUTPUT_PULLDOWN | MUX_MODE1) /* spi0_d0.uart2_txd */ + BONE_P9_22 (PIN_INPUT_PULLUP | MUX_MODE1) /* spi0_sclk.uart2_rxd */ + >; + }; +}; + +&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>; + }; + }; + }; +}; + +&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>; +}; + +&uart2 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&bb_uart2_pins>; +}; + +/ { + 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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-boneblack-wl1835mod-cape.dtsi b/arch/arm/boot/dts/am335x-boneblack-wl1835mod-cape.dtsi new file mode 100644 index 0000000000000..94caa22903017 --- /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 a/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts b/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts new file mode 100644 index 0000000000000..c6db5525752ae --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneblack-wl1835mod.dts @@ -0,0 +1,43 @@ +/* + * 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.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>; +}; + +&sgx { + status = "okay"; +}; + +/* 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 369f34debf15a..790d72ec7aac4 100644 --- a/arch/arm/boot/dts/am335x-boneblack.dts +++ b/arch/arm/boot/dts/am335x-boneblack.dts @@ -9,6 +9,8 @@ #include "am33xx.dtsi" #include "am335x-bone-common.dtsi" +#include "am33xx-overlay-edma-fix.dtsi" +/* #include "am335x-bone-jtag.dtsi" */ / { model = "TI AM335x BeagleBone Black"; @@ -92,6 +94,26 @@ &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>; + }; + }; + }; }; &mcasp0 { @@ -109,15 +131,6 @@ }; / { - 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"; - }; - clk_mcasp0_fixed: clk_mcasp0_fixed { #clock-cells = <0>; compatible = "fixed-clock"; diff --git a/arch/arm/boot/dts/am335x-boneenhanced.dts b/arch/arm/boot/dts/am335x-boneenhanced.dts new file mode 100644 index 0000000000000..9eb0d5136fab8 --- /dev/null +++ b/arch/arm/boot/dts/am335x-boneenhanced.dts @@ -0,0 +1,265 @@ +/* + * 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.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; +}; + +&sgx { + status = "okay"; +}; + +&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 */ + >; + }; + + 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 */ + >; + }; + + 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>; + }; +}; + +&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"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-bonegreen-ctag-face.dts b/arch/arm/boot/dts/am335x-bonegreen-ctag-face.dts new file mode 100644 index 0000000000000..ae06b45565bab --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-ctag-face.dts @@ -0,0 +1,99 @@ +/* + * Base device tree for BeagleBone Green with CTAG face2|4 Audio Card + * + * Author: Henrik Langer + * based on + BeagleBone Black and BeagleBone Green device tree + * + * 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.dtsi" + +/ { + model = "TI AM335x BeagleBone Green AudioCard"; + 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"; +}; + +&sgx { + status = "okay"; +}; + +&am33xx_pinmux { + mcasp0_pins: mcasp0_pins { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_ahclkx */ + 0x19c (PIN_OUTPUT_PULLDOWN | MUX_MODE2) /* mcasp0_axr2 */ + 0x194 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsx */ + 0x190 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_aclkx */ + 0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr */ + 0x078 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* mcasp0_aclkr */ + 0x198 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_axr0 */ + 0x06c (PIN_OUTPUT_PULLDOWN | MUX_MODE7) /* gpio1[27] (enable oscillator) */ + >; + }; + + mcasp0_pins_sleep: mcasp0_pins_sleep { + pinctrl-single,pins = < + 0x1ac (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_ahclkx */ + 0x19c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr2 */ + 0x194 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_fsx */ + 0x190 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_aclkx */ + 0x1a4 (PIN_INPUT_PULLDOWN | MUX_MODE0) /* mcasp0_fsr */ + 0x078 (PIN_INPUT_PULLDOWN | MUX_MODE6) /* mcasp0_aclkr */ + 0x198 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* mcasp0_axr0 */ + 0x06c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* gpio1[27] */ + >; + }; +}; + +&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 */ + 2 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 */ + }; +}; diff --git a/arch/arm/boot/dts/am335x-bonegreen-overlay.dts b/arch/arm/boot/dts/am335x-bonegreen-overlay.dts new file mode 100644 index 0000000000000..efca97d14b1b3 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-overlay.dts @@ -0,0 +1,41 @@ +/* + * 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.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>; +}; + +&sgx { + status = "okay"; +}; + +/* EMMC in reset */ +&gpio1 { + emmc_rst { + gpio-hog; + gpios = <20 0>; + output-high; + line-name = "EMMC ResetN"; + }; +}; diff --git a/arch/arm/boot/dts/am335x-bonegreen-wireless.dts b/arch/arm/boot/dts/am335x-bonegreen-wireless.dts new file mode 100644 index 0000000000000..4bf7dace0ead7 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-wireless.dts @@ -0,0 +1,52 @@ +/* + * 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.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"; +}; + +&sgx { + status = "okay"; +}; diff --git a/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi b/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi new file mode 100644 index 0000000000000..a7d1bbe6bb978 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen-wl1835.dtsi @@ -0,0 +1,126 @@ + +#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/ttyO3"; + 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 */ + + >; + }; +}; + +&mmc3 { + dmas = <&edma_xbar 12 0 1 + &edma_xbar 13 0 2>; + dma-names = "tx", "rx"; + status = "okay"; + vmmc-supply = <&wlan_en_reg>; + 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>; + }; +}; + +&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 new file mode 100644 index 0000000000000..71164f8695761 --- /dev/null +++ b/arch/arm/boot/dts/am335x-bonegreen.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 "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>; +}; + +&mmc2 { + vmmc-supply = <&vmmcsd_fixed>; + pinctrl-names = "default"; + pinctrl-0 = <&emmc_pins>; + bus-width = <8>; + status = "okay"; +}; + +&sgx { + status = "okay"; +}; diff --git a/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 0000000000000..01f9cde642aa9 --- /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 a/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 0000000000000..539409c9fcd81 --- /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 a/arch/arm/boot/dts/am335x-olimex-som.dts b/arch/arm/boot/dts/am335x-olimex-som.dts new file mode 100644 index 0000000000000..e7ee978821be6 --- /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 = "ti,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 a/arch/arm/boot/dts/am335x-peripheral-can0.dtsi b/arch/arm/boot/dts/am335x-peripheral-can0.dtsi new file mode 100644 index 0000000000000..4335e39bf1999 --- /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 a/arch/arm/boot/dts/am335x-peripheral-emmc.dtsi b/arch/arm/boot/dts/am335x-peripheral-emmc.dtsi new file mode 100644 index 0000000000000..603f34ec6dc47 --- /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 a/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 0000000000000..74ddc12c08d87 --- /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 a/arch/arm/boot/dts/am335x-peripheral-ttyS1.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS1.dtsi new file mode 100644 index 0000000000000..f59fa4c5025fa --- /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 a/arch/arm/boot/dts/am335x-peripheral-ttyS2.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS2.dtsi new file mode 100644 index 0000000000000..a25d6cfcb1d6a --- /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 a/arch/arm/boot/dts/am335x-peripheral-ttyS4.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS4.dtsi new file mode 100644 index 0000000000000..adc89f046fbb5 --- /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 a/arch/arm/boot/dts/am335x-peripheral-ttyS5.dtsi b/arch/arm/boot/dts/am335x-peripheral-ttyS5.dtsi new file mode 100644 index 0000000000000..8b42fb02b9b4a --- /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 a/arch/arm/boot/dts/am335x-sancloud-bbe.dts b/arch/arm/boot/dts/am335x-sancloud-bbe.dts new file mode 100644 index 0000000000000..2447d148b36c3 --- /dev/null +++ b/arch/arm/boot/dts/am335x-sancloud-bbe.dts @@ -0,0 +1,201 @@ +/* + * 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.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 a/arch/arm/boot/dts/am335x-som-common.dtsi b/arch/arm/boot/dts/am335x-som-common.dtsi new file mode 100644 index 0000000000000..56e4ce43b82fd --- /dev/null +++ b/arch/arm/boot/dts/am335x-som-common.dtsi @@ -0,0 +1,451 @@ +/* + * 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 50000 1>; + brightness-levels = <0 51 53 56 62 75 101 152 255>; + default-brightness-level = <8>; + }; + +}; + +&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 */ + >; + }; + }; + + +/include/ "tps65217.dtsi" + +&tps { + regulators { + dcdc1_reg: regulator@0 { + regulator-always-on; + }; + + dcdc2_reg: regulator@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 { + /* 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-always-on; + }; + + ldo2_reg: regulator@4 { + regulator-always-on; + }; + + ldo3_reg: regulator@5 { + regulator-always-on; + }; + + ldo4_reg: regulator@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 a/arch/arm/boot/dts/am33xx-overlay-edma-fix.dtsi b/arch/arm/boot/dts/am33xx-overlay-edma-fix.dtsi new file mode 100644 index 0000000000000..88c8d04d19110 --- /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 4ff285b1fe2b9..35b25f20656db 100644 --- a/arch/arm/boot/dts/am33xx.dtsi +++ b/arch/arm/boot/dts/am33xx.dtsi @@ -79,7 +79,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>; @@ -698,6 +698,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 { @@ -728,6 +737,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 { @@ -758,6 +776,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/am57xx-beagle-x15.dts b/arch/arm/boot/dts/am57xx-beagle-x15.dts index a57242bd5fe85..7b5ad77bb825a 100644 --- a/arch/arm/boot/dts/am57xx-beagle-x15.dts +++ b/arch/arm/boot/dts/am57xx-beagle-x15.dts @@ -58,10 +58,22 @@ dsp2_cma_pool: dsp2_cma@9f000000 { compatible = "shared-dma-pool"; - reg = <0x0 0x9f000000 0x0 0x800000>; + reg = <0x0 0x9f000000 0x0 0x1000000>; reusable; status = "okay"; }; + + cmem_block_mem_0: cmem_block_mem@a0000000 { + reg = <0x0 0xa0000000 0x0 0x0a000000>; + no-map; + status = "okay"; + }; + +// cmem_block_mem_1_ocmc3: cmem_block_mem@40500000 { +// reg = <0x0 0x40500000 0x0 0x100000>; +// no-map; +// status = "okay"; +// }; }; vdd_3v3: fixedregulator-vdd_3v3 { @@ -207,6 +219,27 @@ clocks = <&clkout2_clk>; }; }; + + cmem { + compatible = "ti,cmem"; + #address-cells = <1>; + #size-cells = <0>; + + #pool-size-cells = <2>; + + status = "okay"; + + cmem_block_0: cmem_block@0 { + reg = <0>; + memory-region = <&cmem_block_mem_0>; + cmem-buf-pools = <1 0x0 0x0a000000>; + }; + +// cmem_block_1: cmem_block@1 { +// reg = <1>; +// memory-region = <&cmem_block_mem_1_ocmc3>; +// }; + }; }; &dpll_dsp_ck { diff --git a/arch/arm/boot/dts/dra7.dtsi b/arch/arm/boot/dts/dra7.dtsi index 28e7ec60b67f4..76b84fa145f98 100644 --- a/arch/arm/boot/dts/dra7.dtsi +++ b/arch/arm/boot/dts/dra7.dtsi @@ -2372,6 +2372,12 @@ reg = <1>; }; }; + + debugss: debugss { + compatible = "ti,dra7xx-debugss"; + clocks = <&sys_clkin1>; + clock-names = "sysclockin1"; + }; }; thermal_zones: thermal-zones { diff --git a/arch/arm/configs/bb.org_defconfig b/arch/arm/configs/bb.org_defconfig new file mode 100644 index 0000000000000..f483d191abd33 --- /dev/null +++ b/arch/arm/configs/bb.org_defconfig @@ -0,0 +1,2134 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_KERNEL_LZO=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_FHANDLE=y +CONFIG_NO_HZ_IDLE=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +# CONFIG_RCU_BOOST is not set +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=18 +CONFIG_CGROUP_FREEZER=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_MEMCG=y +CONFIG_MEMCG_SWAP=y +CONFIG_MEMCG_KMEM=y +CONFIG_CGROUP_PERF=y +CONFIG_CFS_BANDWIDTH=y +CONFIG_BLK_CGROUP=y +CONFIG_CHECKPOINT_RESTORE=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_SYSCTL_SYSCALL=y +CONFIG_KALLSYMS_ALL=y +CONFIG_BPF_SYSCALL=y +CONFIG_EMBEDDED=y +# CONFIG_COMPAT_BRK is not set +CONFIG_PROFILING=y +CONFIG_KPROBES=y +CONFIG_CC_STACKPROTECTOR_REGULAR=y +CONFIG_MODULES=y +CONFIG_MODULE_FORCE_LOAD=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_BLK_DEV_THROTTLING=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_KARMA_PARTITION=y +CONFIG_CFQ_GROUP_IOSCHED=y +CONFIG_OMAP_RESET_CLOCKS=y +CONFIG_OMAP_MUX_DEBUG=y +CONFIG_SOC_OMAP5=y +CONFIG_SOC_AM33XX=y +CONFIG_SOC_AM43XX=y +CONFIG_SOC_DRA7XX=y +CONFIG_SOC_HAS_OMAP2_SDRC=y +CONFIG_OMAP5_ERRATA_801819=y +CONFIG_ARM_THUMBEE=y +CONFIG_PL310_ERRATA_588369=y +CONFIG_PL310_ERRATA_727915=y +# CONFIG_ARM_ERRATA_643719 is not set +CONFIG_ARM_ERRATA_720789=y +CONFIG_PCI=y +CONFIG_PCI_DRA7XX=y +CONFIG_SMP=y +CONFIG_NR_CPUS=2 +CONFIG_ARM_PSCI=y +CONFIG_PREEMPT_RT_FULL=y +CONFIG_THUMB2_KERNEL=y +CONFIG_KSM=y +CONFIG_FRONTSWAP=y +CONFIG_CMA=y +CONFIG_ZSWAP=y +CONFIG_ZBUD=y +CONFIG_ZSMALLOC=m +CONFIG_SECCOMP=y +CONFIG_ZBOOT_ROM_TEXT=0x0 +CONFIG_ZBOOT_ROM_BSS=0x0 +CONFIG_KEXEC=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +CONFIG_CPUFREQ_VOLTDM=y +# CONFIG_ARM_OMAP2PLUS_CPUFREQ is not set +CONFIG_CPU_IDLE=y +CONFIG_ARM_CPUIDLE=y +CONFIG_KERNEL_MODE_NEON=y +CONFIG_BINFMT_MISC=m +CONFIG_HIBERNATION=y +CONFIG_PM_AUTOSLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_DEBUG=y +CONFIG_PM_ADVANCED_DEBUG=y +CONFIG_APM_EMULATION=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_PACKET_DIAG=m +CONFIG_UNIX=y +CONFIG_UNIX_DIAG=m +CONFIG_XFRM_USER=m +CONFIG_XFRM_SUB_POLICY=y +CONFIG_NET_KEY=m +CONFIG_NET_KEY_MIGRATE=y +CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_FIB_TRIE_STATS=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y +CONFIG_IP_PNP_BOOTP=y +CONFIG_IP_PNP_RARP=y +CONFIG_NET_IPIP=m +CONFIG_NET_IPGRE_DEMUX=m +CONFIG_NET_IPGRE=m +CONFIG_NET_IPGRE_BROADCAST=y +CONFIG_IP_MROUTE=y +CONFIG_IP_MROUTE_MULTIPLE_TABLES=y +CONFIG_IP_PIMSM_V1=y +CONFIG_IP_PIMSM_V2=y +CONFIG_NET_IPVTI=m +CONFIG_NET_FOU_IP_TUNNELS=y +CONFIG_GENEVE=m +CONFIG_INET_AH=m +CONFIG_INET_ESP=m +CONFIG_INET_IPCOMP=m +CONFIG_INET_XFRM_MODE_TRANSPORT=m +CONFIG_INET_XFRM_MODE_TUNNEL=m +CONFIG_INET_XFRM_MODE_BEET=m +CONFIG_INET_LRO=m +CONFIG_INET_DIAG=m +CONFIG_INET_UDP_DIAG=m +CONFIG_TCP_CONG_ADVANCED=y +CONFIG_TCP_CONG_HSTCP=m +CONFIG_TCP_CONG_HYBLA=m +CONFIG_TCP_CONG_SCALABLE=m +CONFIG_TCP_CONG_LP=m +CONFIG_TCP_CONG_VENO=m +CONFIG_TCP_CONG_YEAH=m +CONFIG_TCP_CONG_ILLINOIS=m +CONFIG_TCP_CONG_DCTCP=m +CONFIG_TCP_MD5SIG=y +CONFIG_IPV6=y +CONFIG_IPV6_ROUTER_PREF=y +CONFIG_IPV6_ROUTE_INFO=y +CONFIG_IPV6_OPTIMISTIC_DAD=y +CONFIG_INET6_AH=m +CONFIG_INET6_ESP=m +CONFIG_INET6_IPCOMP=m +CONFIG_IPV6_MIP6=y +CONFIG_INET6_XFRM_MODE_TRANSPORT=m +CONFIG_INET6_XFRM_MODE_TUNNEL=m +CONFIG_INET6_XFRM_MODE_BEET=m +CONFIG_INET6_XFRM_MODE_ROUTEOPTIMIZATION=m +CONFIG_IPV6_VTI=m +CONFIG_IPV6_SIT=m +CONFIG_IPV6_SIT_6RD=y +CONFIG_IPV6_GRE=m +CONFIG_IPV6_MULTIPLE_TABLES=y +CONFIG_IPV6_SUBTREES=y +CONFIG_IPV6_MROUTE=y +CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y +CONFIG_IPV6_PIMSM_V2=y +CONFIG_NETLABEL=y +CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_SECMARK=y +CONFIG_NF_CONNTRACK_ZONES=y +CONFIG_NF_CONNTRACK_EVENTS=y +CONFIG_NF_CONNTRACK_TIMEOUT=y +CONFIG_NF_CONNTRACK_TIMESTAMP=y +CONFIG_NF_CT_PROTO_UDPLITE=m +CONFIG_NF_CONNTRACK_AMANDA=m +CONFIG_NF_CONNTRACK_FTP=m +CONFIG_NF_CONNTRACK_H323=m +CONFIG_NF_CONNTRACK_IRC=m +CONFIG_NF_CONNTRACK_NETBIOS_NS=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_CONNTRACK_PPTP=m +CONFIG_NF_CONNTRACK_SANE=m +CONFIG_NF_CONNTRACK_SIP=m +CONFIG_NF_CONNTRACK_TFTP=m +CONFIG_NF_CT_NETLINK=m +CONFIG_NF_CT_NETLINK_TIMEOUT=m +CONFIG_NF_CT_NETLINK_HELPER=m +CONFIG_NETFILTER_NETLINK_QUEUE_CT=y +CONFIG_NF_TABLES=m +CONFIG_NF_TABLES_INET=m +CONFIG_NFT_EXTHDR=m +CONFIG_NFT_META=m +CONFIG_NFT_CT=m +CONFIG_NFT_RBTREE=m +CONFIG_NFT_HASH=m +CONFIG_NFT_COUNTER=m +CONFIG_NFT_LOG=m +CONFIG_NFT_LIMIT=m +CONFIG_NFT_MASQ=m +CONFIG_NFT_REDIR=m +CONFIG_NFT_NAT=m +CONFIG_NFT_QUEUE=m +CONFIG_NFT_REJECT=m +CONFIG_NFT_COMPAT=m +CONFIG_NETFILTER_XT_SET=m +CONFIG_NETFILTER_XT_TARGET_AUDIT=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_CONNMARK=m +CONFIG_NETFILTER_XT_TARGET_CONNSECMARK=m +CONFIG_NETFILTER_XT_TARGET_CT=m +CONFIG_NETFILTER_XT_TARGET_DSCP=m +CONFIG_NETFILTER_XT_TARGET_HMARK=m +CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m +CONFIG_NETFILTER_XT_TARGET_LED=m +CONFIG_NETFILTER_XT_TARGET_LOG=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFLOG=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_TARGET_TEE=m +CONFIG_NETFILTER_XT_TARGET_TPROXY=m +CONFIG_NETFILTER_XT_TARGET_TRACE=m +CONFIG_NETFILTER_XT_TARGET_SECMARK=m +CONFIG_NETFILTER_XT_TARGET_TCPMSS=m +CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m +CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m +CONFIG_NETFILTER_XT_MATCH_BPF=m +CONFIG_NETFILTER_XT_MATCH_CGROUP=m +CONFIG_NETFILTER_XT_MATCH_CLUSTER=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m +CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m +CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m +CONFIG_NETFILTER_XT_MATCH_CONNMARK=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_CPU=m +CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m +CONFIG_NETFILTER_XT_MATCH_DSCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m +CONFIG_NETFILTER_XT_MATCH_HELPER=m +CONFIG_NETFILTER_XT_MATCH_IPCOMP=m +CONFIG_NETFILTER_XT_MATCH_IPRANGE=m +CONFIG_NETFILTER_XT_MATCH_IPVS=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_NFACCT=m +CONFIG_NETFILTER_XT_MATCH_OSF=m +CONFIG_NETFILTER_XT_MATCH_OWNER=m +CONFIG_NETFILTER_XT_MATCH_POLICY=m +CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_RATEEST=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_RECENT=m +CONFIG_NETFILTER_XT_MATCH_SOCKET=m +CONFIG_NETFILTER_XT_MATCH_STATE=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_NETFILTER_XT_MATCH_TIME=m +CONFIG_NETFILTER_XT_MATCH_U32=m +CONFIG_IP_SET=m +CONFIG_IP_SET_BITMAP_IP=m +CONFIG_IP_SET_BITMAP_IPMAC=m +CONFIG_IP_SET_BITMAP_PORT=m +CONFIG_IP_SET_HASH_IP=m +CONFIG_IP_SET_HASH_IPMARK=m +CONFIG_IP_SET_HASH_IPPORT=m +CONFIG_IP_SET_HASH_IPPORTIP=m +CONFIG_IP_SET_HASH_IPPORTNET=m +CONFIG_IP_SET_HASH_MAC=m +CONFIG_IP_SET_HASH_NETPORTNET=m +CONFIG_IP_SET_HASH_NET=m +CONFIG_IP_SET_HASH_NETNET=m +CONFIG_IP_SET_HASH_NETPORT=m +CONFIG_IP_SET_HASH_NETIFACE=m +CONFIG_IP_SET_LIST_SET=m +CONFIG_IP_VS=m +CONFIG_IP_VS_IPV6=y +CONFIG_IP_VS_PROTO_TCP=y +CONFIG_IP_VS_PROTO_UDP=y +CONFIG_IP_VS_PROTO_ESP=y +CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_SCTP=y +CONFIG_IP_VS_RR=m +CONFIG_IP_VS_WRR=m +CONFIG_IP_VS_LC=m +CONFIG_IP_VS_WLC=m +CONFIG_IP_VS_FO=m +CONFIG_IP_VS_LBLC=m +CONFIG_IP_VS_LBLCR=m +CONFIG_IP_VS_DH=m +CONFIG_IP_VS_SH=m +CONFIG_IP_VS_SED=m +CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_FTP=m +CONFIG_IP_VS_PE_SIP=m +CONFIG_NF_CONNTRACK_IPV4=m +CONFIG_NFT_CHAIN_ROUTE_IPV4=m +CONFIG_NF_TABLES_ARP=m +CONFIG_NF_LOG_ARP=m +CONFIG_NFT_CHAIN_NAT_IPV4=m +CONFIG_NFT_MASQ_IPV4=m +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_TARGET_SYNPROXY=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_TARGET_NETMAP=m +CONFIG_IP_NF_TARGET_REDIRECT=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_CLUSTERIP=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_SECURITY=m +CONFIG_IP_NF_ARPTABLES=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NF_CONNTRACK_IPV6=m +CONFIG_NFT_CHAIN_ROUTE_IPV6=m +CONFIG_NFT_CHAIN_NAT_IPV6=m +CONFIG_NFT_MASQ_IPV6=m +CONFIG_IP6_NF_MATCH_AH=m +CONFIG_IP6_NF_MATCH_EUI64=m +CONFIG_IP6_NF_MATCH_FRAG=m +CONFIG_IP6_NF_MATCH_OPTS=m +CONFIG_IP6_NF_MATCH_HL=m +CONFIG_IP6_NF_MATCH_IPV6HEADER=m +CONFIG_IP6_NF_MATCH_MH=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_MATCH_RT=m +CONFIG_IP6_NF_TARGET_HL=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_TARGET_SYNPROXY=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_IP6_NF_TARGET_NPT=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_NFT_BRIDGE_META=m +CONFIG_NFT_BRIDGE_REJECT=m +CONFIG_NF_LOG_BRIDGE=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_BROUTE=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_BRIDGE_EBT_802_3=m +CONFIG_BRIDGE_EBT_AMONG=m +CONFIG_BRIDGE_EBT_ARP=m +CONFIG_BRIDGE_EBT_IP=m +CONFIG_BRIDGE_EBT_IP6=m +CONFIG_BRIDGE_EBT_LIMIT=m +CONFIG_BRIDGE_EBT_MARK=m +CONFIG_BRIDGE_EBT_PKTTYPE=m +CONFIG_BRIDGE_EBT_STP=m +CONFIG_BRIDGE_EBT_VLAN=m +CONFIG_BRIDGE_EBT_ARPREPLY=m +CONFIG_BRIDGE_EBT_DNAT=m +CONFIG_BRIDGE_EBT_MARK_T=m +CONFIG_BRIDGE_EBT_REDIRECT=m +CONFIG_BRIDGE_EBT_SNAT=m +CONFIG_BRIDGE_EBT_LOG=m +CONFIG_BRIDGE_EBT_NFLOG=m +CONFIG_IP_DCCP=m +CONFIG_NET_DCCPPROBE=m +CONFIG_NET_SCTPPROBE=m +CONFIG_SCTP_COOKIE_HMAC_SHA1=y +CONFIG_RDS=m +CONFIG_RDS_TCP=m +CONFIG_TIPC=m +CONFIG_ATM=m +CONFIG_ATM_CLIP=m +CONFIG_ATM_LANE=m +CONFIG_ATM_MPOA=m +CONFIG_ATM_BR2684=m +CONFIG_L2TP=m +CONFIG_L2TP_DEBUGFS=m +CONFIG_L2TP_V3=y +CONFIG_L2TP_IP=m +CONFIG_L2TP_ETH=m +CONFIG_BRIDGE=m +CONFIG_BRIDGE_VLAN_FILTERING=y +CONFIG_VLAN_8021Q=m +CONFIG_VLAN_8021Q_GVRP=y +CONFIG_VLAN_8021Q_MVRP=y +CONFIG_LLC2=m +CONFIG_ATALK=m +CONFIG_DEV_APPLETALK=m +CONFIG_IPDDP=m +CONFIG_IPDDP_ENCAP=y +CONFIG_PHONET=m +CONFIG_6LOWPAN=m +CONFIG_IEEE802154=m +CONFIG_IEEE802154_6LOWPAN=m +CONFIG_NET_SCHED=y +CONFIG_NET_SCH_CBQ=m +CONFIG_NET_SCH_HTB=m +CONFIG_NET_SCH_HFSC=m +CONFIG_NET_SCH_ATM=m +CONFIG_NET_SCH_PRIO=m +CONFIG_NET_SCH_MULTIQ=m +CONFIG_NET_SCH_RED=m +CONFIG_NET_SCH_SFB=m +CONFIG_NET_SCH_SFQ=m +CONFIG_NET_SCH_TEQL=m +CONFIG_NET_SCH_TBF=m +CONFIG_NET_SCH_GRED=m +CONFIG_NET_SCH_DSMARK=m +CONFIG_NET_SCH_NETEM=m +CONFIG_NET_SCH_DRR=m +CONFIG_NET_SCH_MQPRIO=m +CONFIG_NET_SCH_CHOKE=m +CONFIG_NET_SCH_QFQ=m +CONFIG_NET_SCH_CODEL=m +CONFIG_NET_SCH_FQ_CODEL=m +CONFIG_NET_SCH_FQ=m +CONFIG_NET_SCH_HHF=m +CONFIG_NET_SCH_PIE=m +CONFIG_NET_SCH_INGRESS=m +CONFIG_NET_SCH_PLUG=m +CONFIG_NET_CLS_BASIC=m +CONFIG_NET_CLS_TCINDEX=m +CONFIG_NET_CLS_ROUTE4=m +CONFIG_NET_CLS_FW=m +CONFIG_NET_CLS_U32=m +CONFIG_CLS_U32_PERF=y +CONFIG_CLS_U32_MARK=y +CONFIG_NET_CLS_RSVP=m +CONFIG_NET_CLS_RSVP6=m +CONFIG_NET_CLS_FLOW=m +CONFIG_NET_CLS_CGROUP=m +CONFIG_NET_CLS_BPF=m +CONFIG_NET_EMATCH=y +CONFIG_NET_EMATCH_CMP=m +CONFIG_NET_EMATCH_NBYTE=m +CONFIG_NET_EMATCH_U32=m +CONFIG_NET_EMATCH_META=m +CONFIG_NET_EMATCH_TEXT=m +CONFIG_NET_EMATCH_CANID=m +CONFIG_NET_EMATCH_IPSET=m +CONFIG_NET_CLS_ACT=y +CONFIG_NET_ACT_POLICE=m +CONFIG_NET_ACT_GACT=m +CONFIG_GACT_PROB=y +CONFIG_NET_ACT_MIRRED=m +CONFIG_NET_ACT_IPT=m +CONFIG_NET_ACT_NAT=m +CONFIG_NET_ACT_PEDIT=m +CONFIG_NET_ACT_SIMP=m +CONFIG_NET_ACT_SKBEDIT=m +CONFIG_NET_ACT_CSUM=m +CONFIG_NET_ACT_VLAN=m +CONFIG_NET_ACT_BPF=m +CONFIG_NET_ACT_CONNMARK=m +CONFIG_NET_CLS_IND=y +CONFIG_DCB=y +CONFIG_BATMAN_ADV=m +CONFIG_BATMAN_ADV_DAT=y +CONFIG_BATMAN_ADV_NC=y +CONFIG_BATMAN_ADV_MCAST=y +CONFIG_OPENVSWITCH=m +CONFIG_NETLINK_MMAP=y +CONFIG_NETLINK_DIAG=m +CONFIG_NET_MPLS_GSO=y +CONFIG_MPLS_ROUTING=m +CONFIG_CGROUP_NET_PRIO=y +CONFIG_BPF_JIT=y +CONFIG_NET_PKTGEN=m +CONFIG_NET_DROP_MONITOR=m +CONFIG_CAN=m +CONFIG_CAN_VCAN=m +CONFIG_CAN_SLCAN=m +CONFIG_CAN_SJA1000=m +CONFIG_CAN_SJA1000_ISA=m +CONFIG_CAN_C_CAN=m +CONFIG_CAN_C_CAN_PLATFORM=m +CONFIG_CAN_MCP251X=m +CONFIG_CAN_EMS_USB=m +CONFIG_CAN_ESD_USB2=m +CONFIG_CAN_GS_USB=m +CONFIG_CAN_KVASER_USB=m +CONFIG_CAN_PEAK_USB=m +CONFIG_CAN_8DEV_USB=m +CONFIG_CAN_SOFTING=m +CONFIG_BT=m +CONFIG_BT_RFCOMM=m +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=m +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=m +CONFIG_BT_6LOWPAN=m +CONFIG_BT_HCIBTUSB=m +CONFIG_BT_HCIBTSDIO=m +CONFIG_BT_HCIUART=m +CONFIG_BT_HCIUART_BCSP=y +CONFIG_BT_HCIUART_ATH3K=y +CONFIG_BT_HCIUART_LL=y +CONFIG_BT_HCIUART_3WIRE=y +CONFIG_BT_HCIUART_BCM=y +CONFIG_BT_HCIBCM203X=m +CONFIG_BT_HCIBPA10X=m +CONFIG_BT_HCIBFUSB=m +CONFIG_BT_MRVL=m +CONFIG_BT_MRVL_SDIO=m +CONFIG_BT_ATH3K=m +CONFIG_BT_WILINK=m +CONFIG_RXKAD=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_MAC80211_MESH=y +CONFIG_WIMAX=m +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NET_9P_VIRTIO=m +CONFIG_NFC=m +CONFIG_NFC_DIGITAL=m +CONFIG_NFC_PN533=m +CONFIG_NFC_SIM=m +CONFIG_NFC_PORT100=m +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_EXTRA_FIRMWARE="am335x-pm-firmware.elf am335x-bone-scale-data.bin am335x-evm-scale-data.bin am43x-evm-scale-data.bin" +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=24 +CONFIG_OMAP_OCP2SCP=y +CONFIG_VEXPRESS_CONFIG=y +CONFIG_CONNECTOR=y +CONFIG_MTD=y +CONFIG_MTD_OF_PARTS=m +CONFIG_MTD_AR7_PARTS=m +CONFIG_MTD_BLOCK=m +CONFIG_MTD_BLOCK_RO=m +CONFIG_RFD_FTL=m +CONFIG_SSFDC=m +CONFIG_MTD_OOPS=m +CONFIG_MTD_SWAP=m +CONFIG_MTD_PHYSMAP=m +CONFIG_MTD_PLATRAM=m +CONFIG_MTD_DATAFLASH=m +CONFIG_MTD_M25P80=m +CONFIG_MTD_SST25L=m +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_ECC_BCH=y +CONFIG_MTD_NAND_OMAP2=m +CONFIG_MTD_NAND_NANDSIM=m +CONFIG_MTD_ONENAND=y +CONFIG_MTD_ONENAND_VERIFY_WRITE=y +CONFIG_MTD_ONENAND_2X_PROGRAM=y +CONFIG_MTD_LPDDR=m +CONFIG_MTD_SPI_NOR=m +CONFIG_MTD_UBI=m +CONFIG_MTD_UBI_BLOCK=y +CONFIG_OF_CONFIGFS=y +CONFIG_PARPORT=m +CONFIG_PARPORT_1284=y +CONFIG_BLK_DEV_NULL_BLK=m +CONFIG_ZRAM=m +CONFIG_ZRAM_LZ4_COMPRESS=y +CONFIG_BLK_DEV_LOOP=m +CONFIG_BLK_DEV_DRBD=m +CONFIG_BLK_DEV_NBD=m +CONFIG_BLK_DEV_OSD=m +CONFIG_BLK_DEV_RAM=m +CONFIG_BLK_DEV_RAM_SIZE=16384 +CONFIG_ATA_OVER_ETH=m +CONFIG_VIRTIO_BLK=m +CONFIG_BLK_DEV_RBD=m +CONFIG_AD525X_DPOT=m +CONFIG_AD525X_DPOT_I2C=m +CONFIG_AD525X_DPOT_SPI=m +CONFIG_ICS932S401=m +CONFIG_ENCLOSURE_SERVICES=m +CONFIG_APDS9802ALS=m +CONFIG_ISL29003=m +CONFIG_ISL29020=m +CONFIG_SENSORS_TSL2550=m +CONFIG_SENSORS_BH1780=m +CONFIG_SENSORS_BH1770=m +CONFIG_SENSORS_APDS990X=m +CONFIG_HMC6352=m +CONFIG_DS1682=m +CONFIG_TI_DAC7512=m +CONFIG_BMP085_I2C=m +CONFIG_SRAM=y +CONFIG_BONE_CAPEMGR=y +CONFIG_TIEQEP=m +CONFIG_C2PORT=m +CONFIG_EEPROM_AT24=y +CONFIG_EEPROM_AT25=m +CONFIG_EEPROM_LEGACY=m +CONFIG_EEPROM_MAX6875=m +CONFIG_TI_ST=m +CONFIG_SENSORS_LIS3_I2C=m +CONFIG_ALTERA_STAPL=m +CONFIG_BEAGLEBONE_PINMUX_HELPER=y +CONFIG_RAID_ATTRS=m +CONFIG_BLK_DEV_SD=y +CONFIG_CHR_DEV_ST=m +CONFIG_CHR_DEV_OSST=m +CONFIG_BLK_DEV_SR=m +CONFIG_BLK_DEV_SR_VENDOR=y +CONFIG_CHR_DEV_SG=m +CONFIG_CHR_DEV_SCH=m +CONFIG_SCSI_ENCLOSURE=m +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SCAN_ASYNC=y +CONFIG_SCSI_SPI_ATTRS=m +CONFIG_SCSI_FC_ATTRS=m +CONFIG_SCSI_SAS_LIBSAS=m +CONFIG_SCSI_SAS_ATA=y +CONFIG_SCSI_SRP_ATTRS=m +CONFIG_ISCSI_TCP=m +CONFIG_ISCSI_BOOT_SYSFS=m +CONFIG_SCSI_UFSHCD=m +CONFIG_LIBFC=m +CONFIG_LIBFCOE=m +CONFIG_SCSI_VIRTIO=m +CONFIG_SCSI_DH=m +CONFIG_SCSI_DH_RDAC=m +CONFIG_SCSI_DH_HP_SW=m +CONFIG_SCSI_DH_EMC=m +CONFIG_SCSI_DH_ALUA=m +CONFIG_SCSI_OSD_INITIATOR=m +CONFIG_SCSI_OSD_ULD=m +CONFIG_ATA=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_MD=y +CONFIG_MD_LINEAR=m +CONFIG_MD_RAID0=m +CONFIG_MD_MULTIPATH=m +CONFIG_MD_FAULTY=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_THIN_PROVISIONING=m +CONFIG_DM_CACHE=m +CONFIG_DM_ERA=m +CONFIG_DM_MIRROR=m +CONFIG_DM_LOG_USERSPACE=m +CONFIG_DM_RAID=m +CONFIG_DM_ZERO=m +CONFIG_DM_MULTIPATH=m +CONFIG_DM_MULTIPATH_QL=m +CONFIG_DM_MULTIPATH_ST=m +CONFIG_DM_DELAY=m +CONFIG_DM_UEVENT=y +CONFIG_DM_FLAKEY=m +CONFIG_DM_VERITY=m +CONFIG_DM_SWITCH=m +CONFIG_DM_LOG_WRITES=m +CONFIG_TARGET_CORE=m +CONFIG_TCM_IBLOCK=m +CONFIG_TCM_FILEIO=m +CONFIG_TCM_PSCSI=m +CONFIG_LOOPBACK_TARGET=m +CONFIG_TCM_FC=m +CONFIG_ISCSI_TARGET=m +CONFIG_NETDEVICES=y +CONFIG_BONDING=m +CONFIG_DUMMY=m +CONFIG_EQUALIZER=m +CONFIG_IFB=m +CONFIG_NET_TEAM=m +CONFIG_NET_TEAM_MODE_BROADCAST=m +CONFIG_NET_TEAM_MODE_ROUNDROBIN=m +CONFIG_NET_TEAM_MODE_RANDOM=m +CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=m +CONFIG_NET_TEAM_MODE_LOADBALANCE=m +CONFIG_MACVLAN=m +CONFIG_MACVTAP=m +CONFIG_IPVLAN=m +CONFIG_VXLAN=m +CONFIG_NETCONSOLE=m +CONFIG_NETCONSOLE_DYNAMIC=y +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_VIRTIO_NET=m +CONFIG_NLMON=m +CONFIG_ATM_DUMMY=m +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_CADENCE is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_FARADAY is not set +# CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_INTEL is not set +# CONFIG_NET_VENDOR_MARVELL is not set +CONFIG_ENC28J60=m +# CONFIG_NET_VENDOR_8390 is not set +# CONFIG_NET_VENDOR_QUALCOMM is not set +# CONFIG_NET_VENDOR_ROCKER is not set +# CONFIG_NET_VENDOR_SAMSUNG is not set +# CONFIG_NET_VENDOR_SEEQ is not set +CONFIG_SMC91X=y +CONFIG_SMC911X=y +CONFIG_SMSC911X=y +# CONFIG_NET_VENDOR_STMICRO is not set +CONFIG_TI_CPSW=y +CONFIG_TI_CPTS=y +# CONFIG_NET_VENDOR_VIA is not set +CONFIG_AT803X_PHY=y +CONFIG_AMD_PHY=y +CONFIG_MARVELL_PHY=y +CONFIG_DAVICOM_PHY=y +CONFIG_QSEMI_PHY=y +CONFIG_LXT_PHY=y +CONFIG_CICADA_PHY=y +CONFIG_VITESSE_PHY=y +CONFIG_SMSC_PHY=y +CONFIG_BROADCOM_PHY=y +CONFIG_BCM7XXX_PHY=y +CONFIG_BCM87XX_PHY=y +CONFIG_ICPLUS_PHY=y +CONFIG_REALTEK_PHY=y +CONFIG_NATIONAL_PHY=y +CONFIG_STE10XP=y +CONFIG_LSI_ET1011C_PHY=y +CONFIG_MICREL_PHY=y +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOATM=m +CONFIG_PPPOE=m +CONFIG_PPTP=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_SLIP=m +CONFIG_SLIP_COMPRESSED=y +CONFIG_SLIP_SMART=y +CONFIG_SLIP_MODE_SLIP6=y +CONFIG_USB_CATC=m +CONFIG_USB_KAWETH=m +CONFIG_USB_PEGASUS=y +CONFIG_USB_RTL8150=y +CONFIG_USB_RTL8152=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_AX8817X=m +CONFIG_USB_NET_AX88179_178A=m +CONFIG_USB_NET_CDCETHER=m +CONFIG_USB_NET_CDC_EEM=m +CONFIG_USB_NET_CDC_NCM=m +CONFIG_USB_NET_HUAWEI_CDC_NCM=m +CONFIG_USB_NET_CDC_MBIM=m +CONFIG_USB_NET_DM9601=m +CONFIG_USB_NET_SR9700=m +CONFIG_USB_NET_SR9800=m +CONFIG_USB_NET_SMSC75XX=m +CONFIG_USB_NET_SMSC95XX=m +CONFIG_USB_NET_GL620A=m +CONFIG_USB_NET_NET1080=m +CONFIG_USB_NET_PLUSB=m +CONFIG_USB_NET_MCS7830=m +CONFIG_USB_NET_CDC_SUBSET=m +CONFIG_USB_ALI_M5632=y +CONFIG_USB_AN2720=y +CONFIG_USB_EPSON2888=y +CONFIG_USB_KC2190=y +CONFIG_USB_NET_ZAURUS=m +CONFIG_USB_NET_CX82310_ETH=m +CONFIG_USB_NET_KALMIA=m +CONFIG_USB_NET_QMI_WWAN=m +CONFIG_USB_HSO=m +CONFIG_USB_NET_INT51X1=m +CONFIG_USB_CDC_PHONET=m +CONFIG_USB_IPHETH=m +CONFIG_USB_SIERRA_NET=m +CONFIG_USB_VL600=m +CONFIG_LIBERTAS_THINFIRM=m +CONFIG_LIBERTAS_THINFIRM_USB=m +CONFIG_AT76C50X_USB=m +CONFIG_USB_NET_RNDIS_WLAN=m +CONFIG_RTL8187=m +CONFIG_MAC80211_HWSIM=m +CONFIG_ACC1340=m +CONFIG_ACC1340_WLAN_SDIO=m +CONFIG_ACC1340_WLAN_SPI=m +CONFIG_ATH_CARDS=m +CONFIG_ATH9K=m +CONFIG_ATH9K_HTC=m +CONFIG_CARL9170=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_SDIO=m +CONFIG_ATH6KL_USB=m +CONFIG_AR5523=m +CONFIG_ATH10K=m +CONFIG_B43=m +CONFIG_B43_SDIO=y +CONFIG_B43LEGACY=m +CONFIG_BRCMSMAC=m +CONFIG_BRCMFMAC=m +CONFIG_BRCMFMAC_USB=y +CONFIG_HOSTAP=m +CONFIG_HOSTAP_FIRMWARE=y +CONFIG_IWLWIFI=m +CONFIG_LIBERTAS=m +CONFIG_LIBERTAS_USB=m +CONFIG_LIBERTAS_SDIO=m +CONFIG_LIBERTAS_MESH=y +CONFIG_P54_COMMON=m +CONFIG_P54_USB=m +CONFIG_RT2X00=m +CONFIG_RT2500USB=m +CONFIG_RT73USB=m +CONFIG_RT2800USB=m +CONFIG_RT2800USB_RT3573=y +CONFIG_RT2800USB_RT53XX=y +CONFIG_RT2800USB_RT55XX=y +CONFIG_WL_MEDIATEK=y +CONFIG_MT7601U=m +CONFIG_RTL8192CU=m +# CONFIG_RTLWIFI_DEBUG is not set +CONFIG_WL_TI=y +CONFIG_WL1251=m +CONFIG_WL1251_SPI=m +CONFIG_WL1251_SDIO=m +CONFIG_WL12XX=m +CONFIG_WL18XX=m +CONFIG_WLCORE_SPI=m +CONFIG_WLCORE_SDIO=m +CONFIG_ZD1211RW=m +CONFIG_RSI_91X=m +# CONFIG_RSI_SDIO is not set +CONFIG_WIMAX_I2400M_USB=m +CONFIG_INPUT_SPARSEKMAP=m +CONFIG_INPUT_JOYDEV=y +CONFIG_INPUT_EVDEV=y +CONFIG_KEYBOARD_ADP5588=m +CONFIG_KEYBOARD_QT2160=m +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_LM8323=m +CONFIG_KEYBOARD_MAX7359=m +CONFIG_KEYBOARD_OPENCORES=m +CONFIG_KEYBOARD_STOWAWAY=m +CONFIG_KEYBOARD_OMAP4=m +CONFIG_KEYBOARD_TWL4030=m +CONFIG_MOUSE_PS2=m +CONFIG_MOUSE_PS2_ELANTECH=y +CONFIG_MOUSE_PS2_SENTELIC=y +CONFIG_MOUSE_APPLETOUCH=m +CONFIG_MOUSE_SYNAPTICS_I2C=m +CONFIG_MOUSE_SYNAPTICS_USB=m +CONFIG_INPUT_TABLET=y +CONFIG_TABLET_USB_ACECAD=m +CONFIG_TABLET_USB_AIPTEK=m +CONFIG_TABLET_USB_GTCO=m +CONFIG_TABLET_USB_HANWANG=m +CONFIG_TABLET_USB_KBTAB=m +CONFIG_TABLET_SERIAL_WACOM4=m +CONFIG_INPUT_TOUCHSCREEN=y +CONFIG_TOUCHSCREEN_ADS7846=m +CONFIG_TOUCHSCREEN_AD7877=m +CONFIG_TOUCHSCREEN_AD7879=m +CONFIG_TOUCHSCREEN_AD7879_I2C=m +CONFIG_TOUCHSCREEN_ATMEL_MXT=m +CONFIG_TOUCHSCREEN_DYNAPRO=m +CONFIG_TOUCHSCREEN_HAMPSHIRE=m +CONFIG_TOUCHSCREEN_FUJITSU=m +CONFIG_TOUCHSCREEN_GUNZE=m +CONFIG_TOUCHSCREEN_ELO=m +CONFIG_TOUCHSCREEN_WACOM_W8001=m +CONFIG_TOUCHSCREEN_MCS5000=m +CONFIG_TOUCHSCREEN_MTOUCH=m +CONFIG_TOUCHSCREEN_INEXIO=m +CONFIG_TOUCHSCREEN_MK712=m +CONFIG_TOUCHSCREEN_PENMOUNT=m +CONFIG_TOUCHSCREEN_EDT_FT5X06=y +CONFIG_TOUCHSCREEN_TOUCHRIGHT=m +CONFIG_TOUCHSCREEN_TOUCHWIN=m +CONFIG_TOUCHSCREEN_TI_AM335X_TSC=m +CONFIG_TOUCHSCREEN_WM97XX=m +CONFIG_TOUCHSCREEN_USB_COMPOSITE=m +CONFIG_TOUCHSCREEN_MC13783=m +CONFIG_TOUCHSCREEN_TOUCHIT213=m +CONFIG_TOUCHSCREEN_TSC2005=m +CONFIG_TOUCHSCREEN_TSC2007=m +CONFIG_TOUCHSCREEN_SUR40=m +CONFIG_TOUCHSCREEN_TPS6507X=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_MMA8450=m +CONFIG_INPUT_ATI_REMOTE2=m +CONFIG_INPUT_KEYSPAN_REMOTE=m +CONFIG_INPUT_POWERMATE=m +CONFIG_INPUT_YEALINK=m +CONFIG_INPUT_CM109=m +CONFIG_INPUT_TWL4030_PWRBUTTON=m +CONFIG_INPUT_TWL4030_VIBRA=m +CONFIG_INPUT_TWL6040_VIBRA=m +CONFIG_INPUT_UINPUT=m +CONFIG_SERIO_ALTERA_PS2=m +CONFIG_DEVPTS_MULTIPLE_INSTANCES=y +# CONFIG_LEGACY_PTYS is not set +CONFIG_N_GSM=m +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +# CONFIG_SERIAL_8250_DMA is not set +CONFIG_SERIAL_8250_NR_UARTS=6 +CONFIG_SERIAL_8250_RUNTIME_UARTS=6 +CONFIG_SERIAL_8250_DW=y +CONFIG_SERIAL_8250_OMAP=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_VIRTIO_CONSOLE=m +CONFIG_HW_RANDOM_VIRTIO=m +CONFIG_TCG_TPM=m +CONFIG_TCG_TIS_I2C_ATMEL=m +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_MUX=y +CONFIG_I2C_ARB_GPIO_CHALLENGE=m +CONFIG_I2C_MUX_PCA954x=y +CONFIG_I2C_GPIO=y +CONFIG_I2C_OCORES=m +CONFIG_I2C_PCA_PLATFORM=m +CONFIG_I2C_SIMTEC=m +CONFIG_I2C_DIOLAN_U2C=m +CONFIG_I2C_ROBOTFUZZ_OSIF=m +CONFIG_I2C_TAOS_EVM=m +CONFIG_I2C_TINY_USB=m +CONFIG_I2C_VIPERBOARD=m +CONFIG_SPI=y +CONFIG_SPI_BUTTERFLY=m +CONFIG_SPI_GPIO=y +CONFIG_SPI_LM70_LLP=m +CONFIG_SPI_OMAP24XX=m +CONFIG_SPI_SPIDEV=m +CONFIG_HSI=m +CONFIG_NTP_PPS=y +CONFIG_PPS_CLIENT_LDISC=m +CONFIG_PPS_CLIENT_PARPORT=m +CONFIG_PPS_CLIENT_GPIO=m +CONFIG_PINCTRL_AS3722=y +CONFIG_PINCTRL_SINGLE=y +CONFIG_PINCTRL_TI_IODELAY=y +CONFIG_PINCTRL_PALMAS=y +CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_OF_HELPER=y +CONFIG_GPIO_PCA953X=y +CONFIG_GPIO_PCA953X_IRQ=y +CONFIG_GPIO_PCF857X=y +CONFIG_GPIO_DA9052=m +CONFIG_GPIO_PALMAS=y +CONFIG_GPIO_TWL4030=y +CONFIG_GPIO_TWL6040=y +CONFIG_GPIO_VIPERBOARD=m +CONFIG_W1=y +CONFIG_W1_MASTER_DS2490=m +CONFIG_W1_MASTER_DS2482=m +CONFIG_W1_MASTER_GPIO=m +CONFIG_W1_SLAVE_THERM=m +CONFIG_W1_SLAVE_SMEM=m +CONFIG_W1_SLAVE_DS2431=m +CONFIG_W1_SLAVE_DS2433=m +CONFIG_W1_SLAVE_BQ27000=m +CONFIG_BATTERY_BQ27x00=m +CONFIG_CHARGER_ISP1704=m +CONFIG_CHARGER_BQ2415X=m +CONFIG_POWER_AVS=y +CONFIG_VOLTAGE_DOMAIN_OMAP=y +CONFIG_SENSORS_AD7414=m +CONFIG_SENSORS_AD7418=m +CONFIG_SENSORS_ADM1029=m +CONFIG_SENSORS_ADM9240=m +CONFIG_SENSORS_ADT7411=m +CONFIG_SENSORS_ADT7462=m +CONFIG_SENSORS_ADT7470=m +CONFIG_SENSORS_ADT7475=m +CONFIG_SENSORS_ASC7621=m +CONFIG_SENSORS_ATXP1=m +CONFIG_SENSORS_DS620=m +CONFIG_SENSORS_F71882FG=m +CONFIG_SENSORS_F75375S=m +CONFIG_SENSORS_MC13783_ADC=m +CONFIG_SENSORS_G760A=m +CONFIG_SENSORS_G762=m +CONFIG_SENSORS_GPIO_FAN=y +CONFIG_SENSORS_JC42=m +CONFIG_SENSORS_LINEAGE=m +CONFIG_SENSORS_LTC4151=m +CONFIG_SENSORS_LTC4215=m +CONFIG_SENSORS_LTC4245=m +CONFIG_SENSORS_LTC4261=m +CONFIG_SENSORS_MAX1111=m +CONFIG_SENSORS_MAX16065=m +CONFIG_SENSORS_MAX1668=m +CONFIG_SENSORS_MAX6639=m +CONFIG_SENSORS_MAX6642=m +CONFIG_SENSORS_MAX6650=m +CONFIG_SENSORS_HTU21=m +CONFIG_SENSORS_ADCXX=m +CONFIG_SENSORS_LM70=m +CONFIG_SENSORS_LM73=m +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_LM93=m +CONFIG_SENSORS_LM95241=m +CONFIG_SENSORS_LM95245=m +CONFIG_SENSORS_PC87427=m +CONFIG_SENSORS_NTC_THERMISTOR=m +CONFIG_SENSORS_NCT6683=m +CONFIG_SENSORS_NCT6775=m +CONFIG_SENSORS_SHT21=m +CONFIG_SENSORS_DME1737=m +CONFIG_SENSORS_EMC1403=m +CONFIG_SENSORS_EMC2103=m +CONFIG_SENSORS_EMC6W201=m +CONFIG_SENSORS_SMSC47M192=m +CONFIG_SENSORS_SCH5627=m +CONFIG_SENSORS_SMM665=m +CONFIG_SENSORS_ADS1015=m +CONFIG_SENSORS_ADS7828=m +CONFIG_SENSORS_ADS7871=m +CONFIG_SENSORS_AMC6821=m +CONFIG_SENSORS_THMC50=m +CONFIG_SENSORS_TMP102=y +CONFIG_SENSORS_TMP401=m +CONFIG_SENSORS_TMP421=m +CONFIG_SENSORS_VT1211=m +CONFIG_SENSORS_W83791D=m +CONFIG_SENSORS_W83792D=m +CONFIG_SENSORS_W83793=m +CONFIG_SENSORS_W83795=m +CONFIG_SENSORS_W83L786NG=m +CONFIG_SENSORS_W83627EHF=m +CONFIG_THERMAL=y +CONFIG_THERMAL_GOV_FAIR_SHARE=y +CONFIG_CPU_THERMAL=y +CONFIG_TI_SOC_THERMAL=y +CONFIG_TI_THERMAL=y +CONFIG_OMAP5_THERMAL=y +CONFIG_DRA752_THERMAL=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_NOWAYOUT=y +CONFIG_SOFT_WATCHDOG=m +CONFIG_DA9052_WATCHDOG=m +CONFIG_OMAP_WATCHDOG=y +CONFIG_TWL4030_WATCHDOG=m +CONFIG_MFD_AS3722=y +CONFIG_MFD_AXP20X=y +CONFIG_MFD_DA9052_SPI=y +CONFIG_MFD_DA9052_I2C=y +CONFIG_MFD_MC13XXX_SPI=m +CONFIG_MFD_MC13XXX_I2C=m +CONFIG_MFD_VIPERBOARD=m +CONFIG_MFD_RTSX_USB=m +CONFIG_MFD_SEC_CORE=y +CONFIG_MFD_TI_AM335X_TSCADC=m +CONFIG_MFD_PALMAS=y +CONFIG_MFD_TPS65217=y +CONFIG_MFD_TPS65218=y +CONFIG_MFD_TPS65910=y +CONFIG_TWL4030_CORE=y +CONFIG_TWL4030_POWER=y +CONFIG_TWL6040_CORE=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_REGULATOR_ANATOP=m +CONFIG_REGULATOR_AS3722=y +CONFIG_REGULATOR_AXP20X=m +CONFIG_REGULATOR_DA9052=m +CONFIG_REGULATOR_GPIO=y +CONFIG_REGULATOR_MC13783=m +CONFIG_REGULATOR_MC13892=m +CONFIG_REGULATOR_PALMAS=y +CONFIG_REGULATOR_PBIAS=y +CONFIG_REGULATOR_PWM=y +CONFIG_REGULATOR_S2MPA01=m +CONFIG_REGULATOR_S2MPS11=m +CONFIG_REGULATOR_S5M8767=m +CONFIG_REGULATOR_TI_ABB=y +CONFIG_REGULATOR_TPS65023=y +CONFIG_REGULATOR_TPS6507X=y +CONFIG_REGULATOR_TPS65217=y +CONFIG_REGULATOR_TPS65218=y +CONFIG_REGULATOR_TPS65910=y +CONFIG_REGULATOR_TWL4030=y +CONFIG_REGULATOR_VEXPRESS=m +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_ANALOG_TV_SUPPORT=y +CONFIG_MEDIA_DIGITAL_TV_SUPPORT=y +CONFIG_MEDIA_RADIO_SUPPORT=y +CONFIG_MEDIA_SDR_SUPPORT=y +CONFIG_MEDIA_RC_SUPPORT=y +CONFIG_MEDIA_CONTROLLER=y +CONFIG_VIDEO_V4L2_SUBDEV_API=y +CONFIG_DVB_DYNAMIC_MINORS=y +CONFIG_LIRC=m +CONFIG_RC_DEVICES=y +CONFIG_RC_ATI_REMOTE=m +CONFIG_IR_IMON=m +CONFIG_IR_MCEUSB=m +CONFIG_IR_REDRAT3=m +CONFIG_IR_STREAMZAP=m +CONFIG_IR_IGORPLUGUSB=m +CONFIG_IR_IGUANA=m +CONFIG_IR_TTUSBIR=m +CONFIG_RC_LOOPBACK=m +CONFIG_IR_GPIO_CIR=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=m +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_GL860=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_PWC=m +CONFIG_VIDEO_CPIA2=m +CONFIG_USB_ZR364XX=m +CONFIG_USB_STKWEBCAM=m +CONFIG_USB_S2255=m +CONFIG_VIDEO_USBTV=m +CONFIG_VIDEO_PVRUSB2=m +CONFIG_VIDEO_HDPVR=m +CONFIG_VIDEO_USBVISION=m +CONFIG_VIDEO_STK1160_COMMON=m +CONFIG_VIDEO_STK1160_AC97=y +CONFIG_VIDEO_AU0828=m +CONFIG_VIDEO_AU0828_RC=y +CONFIG_VIDEO_CX231XX=m +CONFIG_VIDEO_CX231XX_ALSA=m +CONFIG_VIDEO_CX231XX_DVB=m +CONFIG_DVB_USB=m +CONFIG_DVB_USB_A800=m +CONFIG_DVB_USB_DIBUSB_MB=m +CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y +CONFIG_DVB_USB_DIBUSB_MC=m +CONFIG_DVB_USB_DIB0700=m +CONFIG_DVB_USB_UMT_010=m +CONFIG_DVB_USB_CXUSB=m +CONFIG_DVB_USB_M920X=m +CONFIG_DVB_USB_DIGITV=m +CONFIG_DVB_USB_VP7045=m +CONFIG_DVB_USB_VP702X=m +CONFIG_DVB_USB_GP8PSK=m +CONFIG_DVB_USB_NOVA_T_USB2=m +CONFIG_DVB_USB_TTUSB2=m +CONFIG_DVB_USB_DTT200U=m +CONFIG_DVB_USB_OPERA1=m +CONFIG_DVB_USB_AF9005=m +CONFIG_DVB_USB_AF9005_REMOTE=m +CONFIG_DVB_USB_PCTV452E=m +CONFIG_DVB_USB_DW2102=m +CONFIG_DVB_USB_CINERGY_T2=m +CONFIG_DVB_USB_DTV5100=m +CONFIG_DVB_USB_FRIIO=m +CONFIG_DVB_USB_AZ6027=m +CONFIG_DVB_USB_TECHNISAT_USB2=m +CONFIG_DVB_USB_V2=m +CONFIG_DVB_USB_AF9015=m +CONFIG_DVB_USB_AF9035=m +CONFIG_DVB_USB_ANYSEE=m +CONFIG_DVB_USB_AU6610=m +CONFIG_DVB_USB_AZ6007=m +CONFIG_DVB_USB_CE6230=m +CONFIG_DVB_USB_EC168=m +CONFIG_DVB_USB_GL861=m +CONFIG_DVB_USB_LME2510=m +CONFIG_DVB_USB_MXL111SF=m +CONFIG_DVB_USB_RTL28XXU=m +CONFIG_DVB_USB_DVBSKY=m +CONFIG_SMS_USB_DRV=m +CONFIG_DVB_B2C2_FLEXCOP_USB=m +CONFIG_VIDEO_EM28XX=m +CONFIG_VIDEO_EM28XX_V4L2=m +CONFIG_VIDEO_EM28XX_ALSA=m +CONFIG_VIDEO_EM28XX_DVB=m +CONFIG_USB_AIRSPY=m +CONFIG_USB_HACKRF=m +CONFIG_USB_MSI2500=m +CONFIG_V4L_PLATFORM_DRIVERS=y +CONFIG_SOC_CAMERA=m +CONFIG_SOC_CAMERA_PLATFORM=m +CONFIG_VIDEO_AM437X_VPFE=m +CONFIG_VIDEO_TI_CAL=m +CONFIG_VIDEO_TI_VIP=m +CONFIG_V4L_MEM2MEM_DRIVERS=y +CONFIG_VIDEO_MEM2MEM_DEINTERLACE=m +CONFIG_VIDEO_TI_VPE=m +CONFIG_V4L_TEST_DRIVERS=y +CONFIG_VIDEO_VIVID=m +CONFIG_SMS_SDIO_DRV=m +CONFIG_RADIO_SI470X=y +CONFIG_USB_SI470X=m +CONFIG_RADIO_SI4713=m +CONFIG_I2C_SI4713=m +CONFIG_USB_MR800=m +CONFIG_RADIO_SHARK=m +CONFIG_RADIO_SHARK2=m +CONFIG_USB_KEENE=m +CONFIG_USB_RAREMONO=m +CONFIG_USB_MA901=m +CONFIG_RADIO_WL128X=m +# CONFIG_MEDIA_SUBDRV_AUTOSELECT is not set +CONFIG_VIDEO_TVAUDIO=m +CONFIG_VIDEO_TDA7432=m +CONFIG_VIDEO_TDA9840=m +CONFIG_VIDEO_TEA6415C=m +CONFIG_VIDEO_TEA6420=m +CONFIG_VIDEO_CS5345=m +CONFIG_VIDEO_WM8739=m +CONFIG_VIDEO_VP27SMPX=m +CONFIG_VIDEO_SAA6588=m +CONFIG_VIDEO_TVP5150=m +CONFIG_VIDEO_SAA717X=m +CONFIG_VIDEO_SAA7127=m +CONFIG_VIDEO_OV2659=m +CONFIG_VIDEO_OV7670=m +CONFIG_VIDEO_OV1063X=m +CONFIG_VIDEO_MT9V011=m +CONFIG_VIDEO_MT9T11X=m +CONFIG_VIDEO_UPD64031A=m +CONFIG_VIDEO_UPD64083=m +CONFIG_VIDEO_SAA6752HS=m +CONFIG_VIDEO_M52790=m +CONFIG_DRM=y +CONFIG_DRM_LOAD_EDID_FIRMWARE=y +CONFIG_DRM_UDL=y +CONFIG_DRM_OMAP=y +CONFIG_DRM_OMAP_NUM_CRTCS=2 +CONFIG_DRM_TILCDC=m +CONFIG_FIRMWARE_EDID=y +CONFIG_FB_TILEBLITTING=y +CONFIG_FB_SMSCUFX=m +CONFIG_FB_UDL=m +CONFIG_FB_SIMPLE=y +CONFIG_OMAP2_DSS=y +CONFIG_OMAP5_DSS_HDMI=y +CONFIG_DISPLAY_ENCODER_OPA362=y +CONFIG_DISPLAY_ENCODER_TPD12S015=y +CONFIG_DISPLAY_DRA7EVM_ENCODER_TPD12S015=m +CONFIG_DISPLAY_ENCODER_SII9022=y +CONFIG_DISPLAY_ENCODER_TC358768=y +CONFIG_DISPLAY_CONNECTOR_DVI=y +CONFIG_DISPLAY_CONNECTOR_HDMI=y +CONFIG_DISPLAY_PANEL_DPI=y +CONFIG_DISPLAY_PANEL_SONY_ACX565AKM=m +CONFIG_DISPLAY_PANEL_TLC59108=y +CONFIG_FB_SSD1307=y +# CONFIG_LCD_CLASS_DEVICE is not set +# CONFIG_BACKLIGHT_GENERIC is not set +CONFIG_BACKLIGHT_PWM=y +CONFIG_BACKLIGHT_GPIO=y +CONFIG_BACKLIGHT_LED=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +CONFIG_SOUND=y +# CONFIG_SOUND_OSS_CORE_PRECLAIM is not set +CONFIG_SND=y +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +CONFIG_SND_MIXER_OSS=m +CONFIG_SND_PCM_OSS=m +CONFIG_SND_SEQUENCER_OSS=y +CONFIG_SND_HRTIMER=m +CONFIG_SND_DYNAMIC_MINORS=y +CONFIG_SND_DUMMY=m +CONFIG_SND_ALOOP=m +CONFIG_SND_MTS64=m +CONFIG_SND_PORTMAN2X4=m +CONFIG_SND_AC97_POWER_SAVE=y +CONFIG_SND_USB_AUDIO=m +CONFIG_SND_USB_UA101=m +CONFIG_SND_USB_CAIAQ=m +CONFIG_SND_USB_CAIAQ_INPUT=y +CONFIG_SND_USB_6FIRE=m +CONFIG_SND_USB_HIFACE=m +CONFIG_SND_BCD2000=m +CONFIG_SND_USB_POD=m +CONFIG_SND_USB_PODHD=m +CONFIG_SND_USB_TONEPORT=m +CONFIG_SND_USB_VARIAX=m +CONFIG_SND_SOC=y +CONFIG_SND_EDMA_SOC=m +CONFIG_SND_AM33XX_SOC_EVM=m +CONFIG_SND_DAVINCI_SOC_CTAG_FACE_2_4=m +CONFIG_SND_SOC_FSL_SSI=m +CONFIG_SND_SOC_FSL_SPDIF=m +CONFIG_SND_SOC_IMX_AUDMUX=m +CONFIG_SND_OMAP_SOC=y +CONFIG_SND_OMAP_SOC_HDMI_AUDIO=m +CONFIG_SND_OMAP_SOC_RX51=m +CONFIG_SND_OMAP_SOC_OMAP_TWL4030=m +CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=m +CONFIG_SND_SOC_AD193X_I2C=m +CONFIG_SND_SOC_HDMI_CODEC=m +CONFIG_SND_SOC_SGTL5000=m +CONFIG_SND_SOC_TLV320AIC23_I2C=m +CONFIG_SND_SOC_TLV320AIC31XX=m +CONFIG_SND_SIMPLE_CARD=m +CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HIDRAW=y +CONFIG_UHID=y +CONFIG_HID_A4TECH=y +CONFIG_HID_ACRUX=m +CONFIG_HID_ACRUX_FF=y +CONFIG_HID_APPLE=y +CONFIG_HID_APPLEIR=m +CONFIG_HID_AUREAL=m +CONFIG_HID_BELKIN=y +CONFIG_HID_BETOP_FF=m +CONFIG_HID_CHERRY=y +CONFIG_HID_CHICONY=y +CONFIG_HID_PRODIKEYS=m +CONFIG_HID_CP2112=m +CONFIG_HID_CYPRESS=y +CONFIG_HID_DRAGONRISE=m +CONFIG_DRAGONRISE_FF=y +CONFIG_HID_EMS_FF=m +CONFIG_HID_ELECOM=m +CONFIG_HID_ELO=m +CONFIG_HID_EZKEY=y +CONFIG_HID_HOLTEK=m +CONFIG_HOLTEK_FF=y +CONFIG_HID_GT683R=m +CONFIG_HID_KEYTOUCH=m +CONFIG_HID_KYE=m +CONFIG_HID_UCLOGIC=m +CONFIG_HID_WALTOP=m +CONFIG_HID_GYRATION=m +CONFIG_HID_ICADE=m +CONFIG_HID_TWINHAN=m +CONFIG_HID_KENSINGTON=y +CONFIG_HID_LCPOWER=m +CONFIG_HID_LENOVO=m +CONFIG_HID_LOGITECH=y +CONFIG_HID_LOGITECH_DJ=y +CONFIG_LOGITECH_FF=y +CONFIG_LOGIRUMBLEPAD2_FF=y +CONFIG_LOGIG940_FF=y +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=y +CONFIG_HID_MONTEREY=y +CONFIG_HID_MULTITOUCH=m +CONFIG_HID_NTRIG=m +CONFIG_HID_ORTEK=m +CONFIG_HID_PANTHERLORD=m +CONFIG_PANTHERLORD_FF=y +CONFIG_HID_PENMOUNT=m +CONFIG_HID_PETALYNX=m +CONFIG_HID_PICOLCD=m +CONFIG_HID_PICOLCD_FB=y +CONFIG_HID_PICOLCD_BACKLIGHT=y +CONFIG_HID_PICOLCD_LEDS=y +CONFIG_HID_PICOLCD_CIR=y +CONFIG_HID_PLANTRONICS=m +CONFIG_HID_PRIMAX=m +CONFIG_HID_ROCCAT=m +CONFIG_HID_SAITEK=m +CONFIG_HID_SAMSUNG=m +CONFIG_HID_SONY=m +CONFIG_SONY_FF=y +CONFIG_HID_SPEEDLINK=m +CONFIG_HID_STEELSERIES=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_RMI=m +CONFIG_HID_GREENASIA=m +CONFIG_GREENASIA_FF=y +CONFIG_HID_SMARTJOYPLUS=m +CONFIG_SMARTJOYPLUS_FF=y +CONFIG_HID_TIVO=m +CONFIG_HID_TOPSEED=m +CONFIG_HID_THINGM=m +CONFIG_HID_THRUSTMASTER=m +CONFIG_THRUSTMASTER_FF=y +CONFIG_HID_WACOM=m +CONFIG_HID_WIIMOTE=m +CONFIG_HID_XINMO=m +CONFIG_HID_ZEROPLUS=m +CONFIG_ZEROPLUS_FF=y +CONFIG_HID_ZYDACRON=m +CONFIG_HID_SENSOR_CUSTOM_SENSOR=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_DYNAMIC_MINORS=y +CONFIG_USB_OTG=y +CONFIG_USB_MON=y +CONFIG_USB_WUSB_CBAF=m +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_U132_HCD=m +CONFIG_USB_HWA_HCD=m +CONFIG_USB_PRINTER=m +CONFIG_USB_TMC=m +CONFIG_USB_STORAGE=m +CONFIG_USB_STORAGE_REALTEK=m +CONFIG_USB_STORAGE_DATAFAB=m +CONFIG_USB_STORAGE_FREECOM=m +CONFIG_USB_STORAGE_ISD200=m +CONFIG_USB_STORAGE_USBAT=m +CONFIG_USB_STORAGE_SDDR09=m +CONFIG_USB_STORAGE_SDDR55=m +CONFIG_USB_STORAGE_JUMPSHOT=m +CONFIG_USB_STORAGE_ALAUDA=m +CONFIG_USB_STORAGE_ONETOUCH=m +CONFIG_USB_STORAGE_KARMA=m +CONFIG_USB_STORAGE_CYPRESS_ATACB=m +CONFIG_USB_STORAGE_ENE_UB6250=m +CONFIG_USB_UAS=m +CONFIG_USB_MDC800=m +CONFIG_USB_MICROTEK=m +CONFIG_USBIP_CORE=m +CONFIG_USBIP_VHCI_HCD=m +CONFIG_USBIP_HOST=m +CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_DSPS=y +CONFIG_USB_DWC3=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_GENERIC=y +CONFIG_USB_SERIAL_SIMPLE=m +CONFIG_USB_SERIAL_AIRCABLE=m +CONFIG_USB_SERIAL_ARK3116=m +CONFIG_USB_SERIAL_BELKIN=m +CONFIG_USB_SERIAL_CH341=m +CONFIG_USB_SERIAL_WHITEHEAT=m +CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m +CONFIG_USB_SERIAL_CP210X=m +CONFIG_USB_SERIAL_CYPRESS_M8=m +CONFIG_USB_SERIAL_EMPEG=m +CONFIG_USB_SERIAL_FTDI_SIO=m +CONFIG_USB_SERIAL_VISOR=m +CONFIG_USB_SERIAL_IPAQ=m +CONFIG_USB_SERIAL_IR=m +CONFIG_USB_SERIAL_EDGEPORT=m +CONFIG_USB_SERIAL_EDGEPORT_TI=m +CONFIG_USB_SERIAL_F81232=m +CONFIG_USB_SERIAL_GARMIN=m +CONFIG_USB_SERIAL_IPW=m +CONFIG_USB_SERIAL_IUU=m +CONFIG_USB_SERIAL_KEYSPAN_PDA=m +CONFIG_USB_SERIAL_KEYSPAN=m +CONFIG_USB_SERIAL_KLSI=m +CONFIG_USB_SERIAL_KOBIL_SCT=m +CONFIG_USB_SERIAL_MCT_U232=m +CONFIG_USB_SERIAL_METRO=m +CONFIG_USB_SERIAL_MOS7720=m +CONFIG_USB_SERIAL_MOS7715_PARPORT=y +CONFIG_USB_SERIAL_MOS7840=m +CONFIG_USB_SERIAL_MXUPORT=m +CONFIG_USB_SERIAL_NAVMAN=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OTI6858=m +CONFIG_USB_SERIAL_QCAUX=m +CONFIG_USB_SERIAL_QUALCOMM=m +CONFIG_USB_SERIAL_SPCP8X5=m +CONFIG_USB_SERIAL_SAFE=m +CONFIG_USB_SERIAL_SIERRAWIRELESS=m +CONFIG_USB_SERIAL_SYMBOL=m +CONFIG_USB_SERIAL_TI=m +CONFIG_USB_SERIAL_CYBERJACK=m +CONFIG_USB_SERIAL_XIRCOM=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_USB_SERIAL_OMNINET=m +CONFIG_USB_SERIAL_OPTICON=m +CONFIG_USB_SERIAL_XSENS_MT=m +CONFIG_USB_SERIAL_WISHBONE=m +CONFIG_USB_SERIAL_SSU100=m +CONFIG_USB_SERIAL_QT2=m +CONFIG_USB_SERIAL_DEBUG=m +CONFIG_USB_EMI62=m +CONFIG_USB_EMI26=m +CONFIG_USB_ADUTUX=m +CONFIG_USB_SEVSEG=m +CONFIG_USB_RIO500=m +CONFIG_USB_LEGOTOWER=m +CONFIG_USB_LCD=m +CONFIG_USB_LED=m +CONFIG_USB_CYPRESS_CY7C63=m +CONFIG_USB_CYTHERM=m +CONFIG_USB_IDMOUSE=m +CONFIG_USB_FTDI_ELAN=m +CONFIG_USB_APPLEDISPLAY=m +CONFIG_USB_SISUSBVGA=m +CONFIG_USB_SISUSBVGA_CON=y +CONFIG_USB_LD=m +CONFIG_USB_TRANCEVIBRATOR=m +CONFIG_USB_IOWARRIOR=m +CONFIG_USB_TEST=m +CONFIG_USB_EHSET_TEST_FIXTURE=m +CONFIG_USB_ISIGHTFW=m +CONFIG_USB_YUREX=m +CONFIG_USB_HSIC_USB3503=m +CONFIG_USB_CHAOSKEY=m +CONFIG_AM335X_PHY_USB=y +CONFIG_USB_GPIO_VBUS=y +CONFIG_USB_ULPI=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS=32 +CONFIG_USB_CONFIGFS=m +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_ZERO=m +CONFIG_USB_AUDIO=m +CONFIG_USB_ETH=m +CONFIG_USB_G_NCM=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_FUNCTIONFS=m +CONFIG_USB_FUNCTIONFS_ETH=y +CONFIG_USB_FUNCTIONFS_RNDIS=y +CONFIG_USB_FUNCTIONFS_GENERIC=y +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_MIDI_GADGET=m +CONFIG_USB_G_PRINTER=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_NOKIA=m +CONFIG_USB_G_ACM_MS=m +CONFIG_USB_G_MULTI=m +CONFIG_USB_G_HID=m +CONFIG_USB_G_DBGP=m +CONFIG_USB_LED_TRIG=y +CONFIG_UWB=m +CONFIG_UWB_I1480U=m +CONFIG_MMC=y +CONFIG_SDIO_UART=m +CONFIG_MMC_SDHCI=m +CONFIG_MMC_SDHCI_PLTFM=m +CONFIG_MMC_OMAP=y +CONFIG_MMC_OMAP_HS=y +CONFIG_MMC_DW=m +CONFIG_MMC_DW_EXYNOS=m +CONFIG_MMC_VUB300=m +CONFIG_MMC_USHC=m +CONFIG_MMC_REALTEK_USB=m +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_GPIO=y +CONFIG_LEDS_LP3944=m +CONFIG_LEDS_LP5523=m +CONFIG_LEDS_PCA955X=m +CONFIG_LEDS_DA9052=m +CONFIG_LEDS_DAC124S085=m +CONFIG_LEDS_PWM=m +CONFIG_LEDS_REGULATOR=m +CONFIG_LEDS_BD2802=m +CONFIG_LEDS_LT3593=m +CONFIG_LEDS_MC13783=m +CONFIG_LEDS_TLC591XX=y +CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_LEDS_TRIGGER_ONESHOT=y +CONFIG_LEDS_TRIGGER_HEARTBEAT=y +CONFIG_LEDS_TRIGGER_BACKLIGHT=y +CONFIG_LEDS_TRIGGER_GPIO=y +CONFIG_LEDS_TRIGGER_DEFAULT_ON=y +CONFIG_LEDS_TRIGGER_TRANSIENT=m +CONFIG_LEDS_TRIGGER_CAMERA=m +CONFIG_ACCESSIBILITY=y +CONFIG_A11Y_BRAILLE_CONSOLE=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_AS3722=y +CONFIG_RTC_DRV_DS1307=y +CONFIG_RTC_DRV_DS1374=m +CONFIG_RTC_DRV_DS1374_WDT=y +CONFIG_RTC_DRV_DS1672=m +CONFIG_RTC_DRV_DS3232=m +CONFIG_RTC_DRV_HYM8563=m +CONFIG_RTC_DRV_MAX6900=m +CONFIG_RTC_DRV_RS5C372=m +CONFIG_RTC_DRV_ISL1208=m +CONFIG_RTC_DRV_ISL12022=m +CONFIG_RTC_DRV_ISL12057=y +CONFIG_RTC_DRV_X1205=m +CONFIG_RTC_DRV_PALMAS=y +CONFIG_RTC_DRV_PCF2127=m +CONFIG_RTC_DRV_PCF8523=y +CONFIG_RTC_DRV_PCF8563=m +CONFIG_RTC_DRV_PCF85063=m +CONFIG_RTC_DRV_PCF8583=m +CONFIG_RTC_DRV_M41T80=m +CONFIG_RTC_DRV_M41T80_WDT=y +CONFIG_RTC_DRV_BQ32K=m +CONFIG_RTC_DRV_TWL4030=y +CONFIG_RTC_DRV_TPS65910=m +CONFIG_RTC_DRV_S35390A=m +CONFIG_RTC_DRV_FM3130=m +CONFIG_RTC_DRV_RX8581=m +CONFIG_RTC_DRV_RX8025=m +CONFIG_RTC_DRV_EM3027=m +CONFIG_RTC_DRV_RV3029C2=m +CONFIG_RTC_DRV_S5M=y +CONFIG_RTC_DRV_M41T93=m +CONFIG_RTC_DRV_M41T94=m +CONFIG_RTC_DRV_DS1305=m +CONFIG_RTC_DRV_DS1343=m +CONFIG_RTC_DRV_DS1347=m +CONFIG_RTC_DRV_DS1390=m +CONFIG_RTC_DRV_MAX6902=m +CONFIG_RTC_DRV_R9701=m +CONFIG_RTC_DRV_RS5C348=m +CONFIG_RTC_DRV_DS3234=m +CONFIG_RTC_DRV_PCF2123=m +CONFIG_RTC_DRV_RX4581=m +CONFIG_RTC_DRV_MCP795=m +CONFIG_RTC_DRV_CMOS=m +CONFIG_RTC_DRV_DS1286=m +CONFIG_RTC_DRV_DS1511=m +CONFIG_RTC_DRV_DS1553=m +CONFIG_RTC_DRV_DS1685_FAMILY=m +CONFIG_RTC_DRV_DS1742=m +CONFIG_RTC_DRV_DS2404=m +CONFIG_RTC_DRV_DA9052=y +CONFIG_RTC_DRV_STK17TA8=m +CONFIG_RTC_DRV_M48T86=m +CONFIG_RTC_DRV_M48T35=m +CONFIG_RTC_DRV_M48T59=m +CONFIG_RTC_DRV_MSM6242=m +CONFIG_RTC_DRV_BQ4802=m +CONFIG_RTC_DRV_RP5C01=m +CONFIG_RTC_DRV_V3020=m +CONFIG_RTC_DRV_OMAP=y +CONFIG_RTC_DRV_MC13XXX=m +CONFIG_RTC_DRV_SNVS=y +CONFIG_RTC_DRV_HID_SENSOR_TIME=m +CONFIG_DMADEVICES=y +CONFIG_DW_DMAC=y +CONFIG_TI_EDMA=y +CONFIG_DMA_OMAP=y +CONFIG_TI_CPPI41=y +CONFIG_ASYNC_TX_DMA=y +CONFIG_UIO=m +CONFIG_UIO_PDRV_GENIRQ=m +CONFIG_UIO_DMEM_GENIRQ=m +CONFIG_VIRT_DRIVERS=y +CONFIG_VIRTIO_BALLOON=m +CONFIG_VIRTIO_INPUT=m +CONFIG_VIRTIO_MMIO=m +CONFIG_STAGING=y +CONFIG_R8712U=m +CONFIG_R8188EU=m +CONFIG_LIS3L02DQ=m +CONFIG_SPEAKUP=m +CONFIG_SPEAKUP_SYNTH_ACNTSA=m +CONFIG_SPEAKUP_SYNTH_APOLLO=m +CONFIG_SPEAKUP_SYNTH_AUDPTR=m +CONFIG_SPEAKUP_SYNTH_BNS=m +CONFIG_SPEAKUP_SYNTH_DECTLK=m +CONFIG_SPEAKUP_SYNTH_DECEXT=m +CONFIG_SPEAKUP_SYNTH_LTLK=m +CONFIG_SPEAKUP_SYNTH_SOFT=m +CONFIG_SPEAKUP_SYNTH_SPKOUT=m +CONFIG_SPEAKUP_SYNTH_TXPRT=m +CONFIG_SPEAKUP_SYNTH_DUMMY=m +CONFIG_ASHMEM=y +CONFIG_ANDROID_TIMED_GPIO=m +CONFIG_SYNC=y +CONFIG_ION=y +CONFIG_FB_TFT=m +CONFIG_FB_TFT_AGM1264K_FL=m +CONFIG_FB_TFT_BD663474=m +CONFIG_FB_TFT_HX8340BN=m +CONFIG_FB_TFT_HX8347D=m +CONFIG_FB_TFT_HX8353D=m +CONFIG_FB_TFT_HX8357D=m +CONFIG_FB_TFT_ILI9163=m +CONFIG_FB_TFT_ILI9320=m +CONFIG_FB_TFT_ILI9325=m +CONFIG_FB_TFT_ILI9340=m +CONFIG_FB_TFT_ILI9341=m +CONFIG_FB_TFT_ILI9481=m +CONFIG_FB_TFT_ILI9486=m +CONFIG_FB_TFT_PCD8544=m +CONFIG_FB_TFT_RA8875=m +CONFIG_FB_TFT_S6D02A1=m +CONFIG_FB_TFT_S6D1121=m +CONFIG_FB_TFT_SSD1289=m +CONFIG_FB_TFT_SSD1305=m +CONFIG_FB_TFT_SSD1306=m +CONFIG_FB_TFT_SSD1325=m +CONFIG_FB_TFT_SSD1331=m +CONFIG_FB_TFT_SSD1351=m +CONFIG_FB_TFT_ST7735R=m +CONFIG_FB_TFT_ST7789V=m +CONFIG_FB_TFT_TINYLCD=m +CONFIG_FB_TFT_TLS8204=m +CONFIG_FB_TFT_UC1611=m +CONFIG_FB_TFT_UC1701=m +CONFIG_FB_TFT_UPD161704=m +CONFIG_FB_TFT_WATTEROTT=m +CONFIG_FB_FLEX=m +CONFIG_FB_TFT_FBTFT_DEVICE=m +CONFIG_COMMON_CLK_S2MPS11=m +CONFIG_CLK_TWL6040=m +CONFIG_COMMON_CLK_PALMAS=y +CONFIG_HWSPINLOCK_OMAP=y +CONFIG_OMAP2PLUS_MBOX=y +CONFIG_OMAP_IOMMU=y +CONFIG_OMAP_IOMMU_DEBUG=y +CONFIG_OMAP_REMOTEPROC=m +CONFIG_OMAP_REMOTEPROC_WATCHDOG=y +CONFIG_WKUP_M3_RPROC=y +CONFIG_PRUSS_REMOTEPROC=m +CONFIG_RPMSG_RPC=m +CONFIG_RPMSG_PRU=m +CONFIG_SOC_TI=y +CONFIG_WKUP_M3_IPC=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_EXTCON=y +CONFIG_EXTCON_GPIO=y +CONFIG_EXTCON_PALMAS=y +CONFIG_EXTCON_USB_GPIO=y +CONFIG_TI_EMIF=y +CONFIG_TI_EMIF_SRAM=y +CONFIG_BMA180=m +CONFIG_BMC150_ACCEL=m +CONFIG_HID_SENSOR_ACCEL_3D=m +CONFIG_IIO_ST_ACCEL_3AXIS=m +CONFIG_KXSD9=m +CONFIG_MMA8452=m +CONFIG_KXCJK1013=m +CONFIG_MMA9551=m +CONFIG_MMA9553=m +CONFIG_AD7266=m +CONFIG_AD7291=m +CONFIG_AD7298=m +CONFIG_AD7476=m +CONFIG_AD7791=m +CONFIG_AD7793=m +CONFIG_AD7887=m +CONFIG_AD7923=m +CONFIG_AD799X=m +CONFIG_AXP288_ADC=m +CONFIG_CC10001_ADC=m +CONFIG_MAX1027=m +CONFIG_MAX1363=m +CONFIG_MCP320X=m +CONFIG_MCP3422=m +CONFIG_NAU7802=m +CONFIG_TI_ADC081C=m +CONFIG_TI_ADC128S052=m +CONFIG_TI_AM335X_ADC=m +CONFIG_TWL4030_MADC=m +CONFIG_TWL6030_GPADC=m +CONFIG_VF610_ADC=m +CONFIG_VIPERBOARD_ADC=m +CONFIG_AD8366=m +CONFIG_AD5064=m +CONFIG_AD5360=m +CONFIG_AD5380=m +CONFIG_AD5421=m +CONFIG_AD5446=m +CONFIG_AD5449=m +CONFIG_AD5504=m +CONFIG_AD5624R_SPI=m +CONFIG_AD5686=m +CONFIG_AD5755=m +CONFIG_AD5764=m +CONFIG_AD5791=m +CONFIG_AD7303=m +CONFIG_MAX517=m +CONFIG_MAX5821=m +CONFIG_MCP4725=m +CONFIG_MCP4922=m +CONFIG_AD9523=m +CONFIG_ADF4350=m +CONFIG_ADIS16080=m +CONFIG_ADIS16130=m +CONFIG_ADIS16136=m +CONFIG_ADIS16260=m +CONFIG_ADXRS450=m +CONFIG_BMG160=m +CONFIG_HID_SENSOR_GYRO_3D=m +CONFIG_IIO_ST_GYRO_3AXIS=m +CONFIG_ITG3200=m +CONFIG_DHT11=m +CONFIG_SI7005=m +CONFIG_SI7020=m +CONFIG_ADIS16400=m +CONFIG_ADIS16480=m +CONFIG_KMX61=m +CONFIG_INV_MPU6050_IIO=m +CONFIG_ADJD_S311=m +CONFIG_AL3320A=m +CONFIG_APDS9300=m +CONFIG_CM32181=m +CONFIG_CM3232=m +CONFIG_CM3323=m +CONFIG_CM36651=m +CONFIG_GP2AP020A00F=m +CONFIG_ISL29125=m +CONFIG_HID_SENSOR_ALS=m +CONFIG_HID_SENSOR_PROX=m +CONFIG_JSA1212=m +CONFIG_LTR501=m +CONFIG_TCS3414=m +CONFIG_TCS3472=m +CONFIG_SENSORS_TSL2563=m +CONFIG_TSL4531=m +CONFIG_VCNL4000=m +CONFIG_AK09911=m +CONFIG_MAG3110=m +CONFIG_HID_SENSOR_MAGNETOMETER_3D=m +CONFIG_IIO_ST_MAGN_3AXIS=m +CONFIG_HID_SENSOR_INCLINOMETER_3D=m +CONFIG_HID_SENSOR_DEVICE_ROTATION=m +CONFIG_IIO_INTERRUPT_TRIGGER=m +CONFIG_IIO_SYSFS_TRIGGER=m +CONFIG_BMP280=m +CONFIG_HID_SENSOR_PRESS=m +CONFIG_MPL115=m +CONFIG_MPL3115=m +CONFIG_MS5611=m +CONFIG_MS5611_I2C=m +CONFIG_MS5611_SPI=m +CONFIG_IIO_ST_PRESS=m +CONFIG_T5403=m +CONFIG_AS3935=m +CONFIG_SX9500=m +CONFIG_MLX90614=m +CONFIG_TMP006=m +CONFIG_PWM_TIECAP=m +CONFIG_PWM_TIEHRPWM=m +CONFIG_PWM_TWL=m +CONFIG_PWM_TWL_LED=m +CONFIG_OMAP_USB2=y +CONFIG_TI_PIPE3=y +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_NVMEM=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_EXT4_ENCRYPTION=y +CONFIG_REISERFS_FS=m +CONFIG_REISERFS_FS_XATTR=y +CONFIG_REISERFS_FS_POSIX_ACL=y +CONFIG_REISERFS_FS_SECURITY=y +CONFIG_JFS_FS=m +CONFIG_JFS_POSIX_ACL=y +CONFIG_JFS_SECURITY=y +CONFIG_XFS_FS=y +CONFIG_XFS_QUOTA=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_XFS_RT=y +CONFIG_GFS2_FS=m +CONFIG_GFS2_FS_LOCKING_DLM=y +CONFIG_OCFS2_FS=m +CONFIG_BTRFS_FS=y +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_NILFS2_FS=m +CONFIG_F2FS_FS=y +CONFIG_F2FS_FS_SECURITY=y +CONFIG_FANOTIFY=y +CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS4_FS=y +CONFIG_FUSE_FS=y +CONFIG_CUSE=m +CONFIG_OVERLAY_FS=y +CONFIG_FSCACHE_STATS=y +CONFIG_CACHEFILES=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y +CONFIG_VFAT_FS=y +CONFIG_NTFS_FS=m +CONFIG_NTFS_RW=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_ADFS_FS=m +CONFIG_AFFS_FS=m +CONFIG_ECRYPT_FS=m +CONFIG_ECRYPT_FS_MESSAGING=y +CONFIG_HFS_FS=m +CONFIG_HFSPLUS_FS=m +CONFIG_BEFS_FS=m +CONFIG_BFS_FS=m +CONFIG_EFS_FS=m +CONFIG_JFFS2_FS=m +CONFIG_JFFS2_SUMMARY=y +CONFIG_JFFS2_FS_XATTR=y +CONFIG_JFFS2_COMPRESSION_OPTIONS=y +CONFIG_JFFS2_LZO=y +CONFIG_UBIFS_FS=m +CONFIG_UBIFS_FS_ADVANCED_COMPR=y +CONFIG_LOGFS=m +CONFIG_SQUASHFS=m +CONFIG_SQUASHFS_XATTR=y +CONFIG_SQUASHFS_LZ4=y +CONFIG_SQUASHFS_LZO=y +CONFIG_SQUASHFS_XZ=y +CONFIG_VXFS_FS=m +CONFIG_MINIX_FS=m +CONFIG_OMFS_FS=m +CONFIG_QNX4FS_FS=m +CONFIG_QNX6FS_FS=m +CONFIG_ROMFS_FS=m +CONFIG_ROMFS_BACKED_BY_BOTH=y +CONFIG_SYSV_FS=m +CONFIG_UFS_FS=m +CONFIG_EXOFS_FS=m +CONFIG_AUFS_FS=m +CONFIG_AUFS_EXPORT=y +CONFIG_AUFS_XATTR=y +CONFIG_NFS_FS=y +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=y +CONFIG_NFS_SWAP=y +CONFIG_NFS_V4_1=y +CONFIG_NFS_V4_2=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_NFSD_V4_SECURITY_LABEL=y +CONFIG_SUNRPC_DEBUG=y +CONFIG_CEPH_FS=m +CONFIG_CEPH_FSCACHE=y +CONFIG_CEPH_FS_POSIX_ACL=y +CONFIG_CIFS=m +CONFIG_CIFS_WEAK_PW_HASH=y +CONFIG_CIFS_UPCALL=y +CONFIG_CIFS_XATTR=y +CONFIG_CIFS_POSIX=y +CONFIG_CIFS_ACL=y +CONFIG_CIFS_DFS_UPCALL=y +CONFIG_CIFS_SMB2=y +CONFIG_CIFS_FSCACHE=y +CONFIG_NCP_FS=m +CONFIG_NCPFS_PACKET_SIGNING=y +CONFIG_NCPFS_IOCTL_LOCKING=y +CONFIG_NCPFS_STRONG=y +CONFIG_NCPFS_NFS_NS=y +CONFIG_NCPFS_OS2_NS=y +CONFIG_NCPFS_NLS=y +CONFIG_NCPFS_EXTRAS=y +CONFIG_CODA_FS=m +CONFIG_AFS_FS=m +CONFIG_AFS_FSCACHE=y +CONFIG_9P_FS=m +CONFIG_9P_FSCACHE=y +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_9P_FS_SECURITY=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_737=m +CONFIG_NLS_CODEPAGE_775=m +CONFIG_NLS_CODEPAGE_850=m +CONFIG_NLS_CODEPAGE_852=m +CONFIG_NLS_CODEPAGE_855=m +CONFIG_NLS_CODEPAGE_857=m +CONFIG_NLS_CODEPAGE_860=m +CONFIG_NLS_CODEPAGE_861=m +CONFIG_NLS_CODEPAGE_862=m +CONFIG_NLS_CODEPAGE_863=m +CONFIG_NLS_CODEPAGE_864=m +CONFIG_NLS_CODEPAGE_865=m +CONFIG_NLS_CODEPAGE_866=m +CONFIG_NLS_CODEPAGE_869=m +CONFIG_NLS_CODEPAGE_936=m +CONFIG_NLS_CODEPAGE_950=m +CONFIG_NLS_CODEPAGE_932=m +CONFIG_NLS_CODEPAGE_949=m +CONFIG_NLS_CODEPAGE_874=m +CONFIG_NLS_ISO8859_8=m +CONFIG_NLS_CODEPAGE_1250=m +CONFIG_NLS_CODEPAGE_1251=m +CONFIG_NLS_ASCII=m +CONFIG_NLS_ISO8859_1=y +CONFIG_NLS_ISO8859_2=m +CONFIG_NLS_ISO8859_3=m +CONFIG_NLS_ISO8859_4=m +CONFIG_NLS_ISO8859_5=m +CONFIG_NLS_ISO8859_6=m +CONFIG_NLS_ISO8859_7=m +CONFIG_NLS_ISO8859_9=m +CONFIG_NLS_ISO8859_13=m +CONFIG_NLS_ISO8859_14=m +CONFIG_NLS_ISO8859_15=m +CONFIG_NLS_KOI8_R=m +CONFIG_NLS_KOI8_U=m +CONFIG_NLS_MAC_ROMAN=m +CONFIG_NLS_MAC_CELTIC=m +CONFIG_NLS_MAC_CENTEURO=m +CONFIG_NLS_MAC_CROATIAN=m +CONFIG_NLS_MAC_CYRILLIC=m +CONFIG_NLS_MAC_GAELIC=m +CONFIG_NLS_MAC_GREEK=m +CONFIG_NLS_MAC_ICELAND=m +CONFIG_NLS_MAC_INUIT=m +CONFIG_NLS_MAC_ROMANIAN=m +CONFIG_NLS_MAC_TURKISH=m +CONFIG_DLM=m +CONFIG_DLM_DEBUG=y +CONFIG_PRINTK_TIME=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_STRIP_ASM_SYMS=y +CONFIG_UNUSED_SYMBOLS=y +CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x01b6 +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_SCHEDSTATS=y +CONFIG_SCHED_STACK_END_CHECK=y +CONFIG_TIMER_STATS=y +CONFIG_DEBUG_LIST=y +# CONFIG_RCU_CPU_STALL_INFO is not set +CONFIG_FTRACE_SYSCALLS=y +CONFIG_TRACER_SNAPSHOT=y +CONFIG_STACK_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +CONFIG_UPROBE_EVENT=y +CONFIG_SAMPLES=y +CONFIG_SAMPLE_RPMSG_CLIENT=m +CONFIG_KGDB=y +CONFIG_KGDB_KDB=y +CONFIG_KDB_KEYBOARD=y +CONFIG_SECURITY=y +CONFIG_SECURITY_NETWORK_XFRM=y +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_TOMOYO=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_SECURITY_YAMA=y +CONFIG_SECURITY_YAMA_STACKED=y +CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_CRYPTO_PCRYPT=m +CONFIG_CRYPTO_TEST=m +CONFIG_CRYPTO_LRW=m +CONFIG_CRYPTO_XCBC=m +CONFIG_CRYPTO_VMAC=m +CONFIG_CRYPTO_CRC32=m +CONFIG_CRYPTO_RMD128=m +CONFIG_CRYPTO_RMD160=m +CONFIG_CRYPTO_RMD256=m +CONFIG_CRYPTO_RMD320=m +CONFIG_CRYPTO_TGR192=m +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_ANUBIS=m +CONFIG_CRYPTO_BLOWFISH=m +CONFIG_CRYPTO_CAMELLIA=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_CAST6=m +CONFIG_CRYPTO_KHAZAD=m +CONFIG_CRYPTO_SALSA20=m +CONFIG_CRYPTO_SEED=m +CONFIG_CRYPTO_SERPENT=m +CONFIG_CRYPTO_TEA=m +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_ZLIB=m +CONFIG_CRYPTO_LZ4=m +CONFIG_CRYPTO_LZ4HC=m +CONFIG_CRYPTO_USER_API_HASH=y +CONFIG_CRYPTO_USER_API_SKCIPHER=y +CONFIG_CRYPTO_DEV_OMAP_SHAM=y +CONFIG_CRYPTO_DEV_OMAP_AES=y +CONFIG_CRYPTO_DEV_OMAP_DES=y +CONFIG_ARM_CRYPTO=y +CONFIG_CRYPTO_SHA1_ARM_NEON=m +CONFIG_CRYPTO_SHA256_ARM=m +CONFIG_CRYPTO_SHA512_ARM_NEON=m +CONFIG_CRYPTO_AES_ARM_BS=m +# CONFIG_XZ_DEC_X86 is not set +# CONFIG_XZ_DEC_POWERPC is not set +# CONFIG_XZ_DEC_IA64 is not set +# CONFIG_XZ_DEC_SPARC is not set diff --git a/arch/arm/kernel/topology.c b/arch/arm/kernel/topology.c index 08b7847bf9124..ab46d57a01d01 100644 --- a/arch/arm/kernel/topology.c +++ b/arch/arm/kernel/topology.c @@ -119,7 +119,7 @@ static void __init parse_dt_topology(void) rate = of_get_property(cn, "clock-frequency", &len); if (!rate || len != 4) { - pr_err("%s missing clock-frequency property\n", + pr_info("%s missing clock-frequency property\n", cn->full_name); continue; } diff --git a/arch/arm/mach-omap2/omap_device.c b/arch/arm/mach-omap2/omap_device.c index 2f2eb2a193684..5797ade8ccaf6 100644 --- a/arch/arm/mach-omap2/omap_device.c +++ b/arch/arm/mach-omap2/omap_device.c @@ -128,8 +128,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"); @@ -180,6 +180,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); @@ -212,6 +232,26 @@ static int _omap_device_check_reidle_hwmods(struct omap_device *od) return 0; } +static void _omap_device_cleanup(struct omap_device *od) +{ + struct omap_hwmod *oh; + int i; + + for (i = 0; i < od->hwmods_cnt; i++) { + + oh = od->hwmods[i]; + + /* shutdown hwmods */ + omap_hwmod_shutdown(oh); + + /* we don't remove clocks cause there's no API to do so */ + /* no harm done, since they will not be created next time */ + } + + /* cleanup the structure now */ + omap_device_delete(od); +} + static int _omap_device_notifier_call(struct notifier_block *nb, unsigned long event, void *dev) { @@ -219,9 +259,12 @@ static int _omap_device_notifier_call(struct notifier_block *nb, struct omap_device *od; switch (event) { - case BUS_NOTIFY_DEL_DEVICE: - if (pdev->archdata.od) - omap_device_delete(pdev->archdata.od); + case BUS_NOTIFY_UNBOUND_DRIVER: + /* NOTIFY_DEL_DEVICE is not the right call... */ + od = pdev->archdata.od; + pdev->archdata.od = NULL; + if (od) + _omap_device_cleanup(od); break; case BUS_NOTIFY_BOUND_DRIVER: od = to_omap_device(pdev); diff --git a/arch/arm/mach-omap2/opp33xx_data.c b/arch/arm/mach-omap2/opp33xx_data.c index 0e07186f64edf..1f8c77216dc7a 100644 --- a/arch/arm/mach-omap2/opp33xx_data.c +++ b/arch/arm/mach-omap2/opp33xx_data.c @@ -14,6 +14,7 @@ */ #include #include +#include #include "control.h" #include "omap_opp_data.h" @@ -49,7 +50,7 @@ static struct omap_opp_def am33xx_es2_x_opp_list[] __initdata = { /* MPU OPP4 - OPPTurbo */ OPP_INITIALIZER("mpu", true, 800000000, 1260000), /* MPU OPP5 - OPPNitro */ - OPP_INITIALIZER("mpu", false, 1000000000, 1325000), + OPP_INITIALIZER("mpu", true, 1000000000, 1325000), }; /* From AM335x TRM, SPRUH73H, Section 9.3.50 */ @@ -82,6 +83,9 @@ int __init am33xx_opp_init(void) if (WARN(!soc_is_am33xx(), "Cannot init OPPs: unsupported SoC.\n")) return r; + if (of_machine_is_compatible("ti,am335x-olimex-som")) + return r; + rev = omap_rev(); switch (rev) { diff --git a/arch/arm/mach-omap2/sleep33xx.S b/arch/arm/mach-omap2/sleep33xx.S index 26dba595e3f62..3cfc1ce644dde 100644 --- a/arch/arm/mach-omap2/sleep33xx.S +++ b/arch/arm/mach-omap2/sleep33xx.S @@ -36,7 +36,9 @@ ENTRY(am33xx_do_wfi) stmfd sp!, {r4 - r11, lr} @ save registers on stack - str r0, wfi_flags +ARM( str r0, wfi_flags ) +THUMB( adr r2, wfi_flags ) +THUMB( str r0, [r2] ) /* Only necessary if PER is losing context */ ldr r2, wfi_flags @@ -200,7 +202,9 @@ wait_emif_enable1: cmp r1, r2 bne wait_emif_enable1 - adr sp, temp_stack +ARM( adr sp, temp_stack ) +THUMB( adr r1, temp_stack ) +THUMB( mov sp, r1 ) ldr r1, ti_emif_restore_context blx r1 diff --git a/arch/arm/mach-omap2/sleep43xx.S b/arch/arm/mach-omap2/sleep43xx.S index 827f687c3ae77..98b17e723cc1b 100644 --- a/arch/arm/mach-omap2/sleep43xx.S +++ b/arch/arm/mach-omap2/sleep43xx.S @@ -78,12 +78,16 @@ ENTRY(am43xx_do_wfi) stmfd sp!, {r4 - r11, lr} @ save registers on stack - str r0, wfi_flags +ARM( str r0, wfi_flags ) +THUMB( adr r1, wfi_flags ) +THUMB( str r0, [r1] ) /* Retrieve l2 cache virt address BEFORE we shut off EMIF */ ldr r1, get_l2cache_base blx r1 - str r0, l2_base_virt +ARM( str r0, l2_base_virt ) +THUMB( adr r1, l2_base_virt ) +THUMB( str r0, [r1] ) /* * Flush all data from the L1 and L2 data cache before disabling diff --git a/arch/arm/mach-omap2/voltage.c b/arch/arm/mach-omap2/voltage.c index cba8cada8c81a..81cf6eb329e15 100644 --- a/arch/arm/mach-omap2/voltage.c +++ b/arch/arm/mach-omap2/voltage.c @@ -235,7 +235,7 @@ int __init omap_voltage_late_init(void) struct voltagedomain *voltdm; if (list_empty(&voltdm_list)) { - pr_err("%s: Voltage driver support not added\n", + pr_info("%s: Voltage driver support not added\n", __func__); return -EINVAL; } diff --git a/drivers/Kconfig b/drivers/Kconfig index c0cc96bab9e74..69d730501e48d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -182,4 +182,6 @@ source "drivers/thunderbolt/Kconfig" source "drivers/android/Kconfig" +source "drivers/nvmem/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 08749aa6f78d6..55fb12750587f 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -166,3 +166,4 @@ obj-$(CONFIG_RAS) += ras/ obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/ obj-$(CONFIG_ANDROID) += android/ +obj-$(CONFIG_NVMEM) += nvmem/ diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 1c76dcb502cf3..4b6a7ade5df79 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2612,6 +2612,30 @@ int regmap_get_val_bytes(struct regmap *map) } EXPORT_SYMBOL_GPL(regmap_get_val_bytes); +/** + * regmap_get_max_register(): Report the max register value + * + * Report the max register value, mainly intended to for use by + * generic infrastructure built on top of regmap. + */ +int regmap_get_max_register(struct regmap *map) +{ + return map->max_register ? map->max_register : -EINVAL; +} +EXPORT_SYMBOL_GPL(regmap_get_max_register); + +/** + * regmap_get_reg_stride(): Report the register address stride + * + * Report the register address stride, mainly intended to for use by + * generic infrastructure built on top of regmap. + */ +int regmap_get_reg_stride(struct regmap *map) +{ + return map->reg_stride; +} +EXPORT_SYMBOL_GPL(regmap_get_reg_stride); + int regmap_parse_val(struct regmap *map, const void *buf, unsigned int *val) { diff --git a/drivers/block/loop.c b/drivers/block/loop.c index cef6fa83a2740..20fc2beb72fc6 100644 --- a/drivers/block/loop.c +++ b/drivers/block/loop.c @@ -538,6 +538,24 @@ static inline int is_loop_device(struct file *file) return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR; } +/* + * for AUFS + * no get/put for file. + */ +struct file *loop_backing_file(struct super_block *sb) +{ + struct file *ret; + struct loop_device *l; + + ret = NULL; + if (MAJOR(sb->s_dev) == LOOP_MAJOR) { + l = sb->s_bdev->bd_disk->private_data; + ret = l->lo_backing_file; + } + return ret; +} +EXPORT_SYMBOL_GPL(loop_backing_file); + /* loop sysfs attributes */ static ssize_t loop_attr_show(struct device *dev, char *page, diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d947b26e3e564..dae4db4925977 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -85,6 +85,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 7b598a083849f..1cd481e8c426e 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 a/drivers/gpio/gpio-of-helper.c b/drivers/gpio/gpio-of-helper.c new file mode 100644 index 0000000000000..a9f260bd2fee6 --- /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/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed63a001d..d19bd802b63b2 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 2c72eb584ab7c..5a509aed2c002 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 a/drivers/gpu/drm/i2c/adihdmi.h b/drivers/gpu/drm/i2c/adihdmi.h new file mode 100644 index 0000000000000..58d9a2b274ea0 --- /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 a/drivers/gpu/drm/i2c/adihdmi_drv.c b/drivers/gpu/drm/i2c/adihdmi_drv.c new file mode 100644 index 0000000000000..907bdf98c4629 --- /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_default 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/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig index 8394a0b3993ec..78beafb0742c2 100644 --- a/drivers/gpu/drm/tilcdc/Kconfig +++ b/drivers/gpu/drm/tilcdc/Kconfig @@ -12,3 +12,15 @@ config DRM_TILCDC Choose this option if you have an TI SoC with LCDC display controller, for example AM33xx in beagle-bone, DA8xx, or OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver. + +config DRM_TILCDC_SLAVE_COMPAT + bool "Support device tree blobs using TI LCDC Slave binding" + depends on DRM_TILCDC + default y + select OF_RESOLVE + select OF_OVERLAY + help + Choose this option if you need a kernel that is compatible + with device tree blobs using the obsolete "ti,tilcdc,slave" + binding. If you find "ti,tilcdc,slave"-string from your DTB, + you probably need this. Otherwise you do not. diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile index 7d2eefe94bf7f..deeca4869d941 100644 --- a/drivers/gpu/drm/tilcdc/Makefile +++ b/drivers/gpu/drm/tilcdc/Makefile @@ -3,11 +3,14 @@ ifeq (, $(findstring -W,$(EXTRA_CFLAGS))) ccflags-y += -Werror endif +obj-$(CONFIG_DRM_TILCDC_SLAVE_COMPAT) += tilcdc_slave_compat.o \ + tilcdc_slave_compat.dtb.o + tilcdc-y := \ tilcdc_crtc.o \ tilcdc_tfp410.o \ - tilcdc_slave.o \ tilcdc_panel.o \ + tilcdc_external.o \ tilcdc_drv.o obj-$(CONFIG_DRM_TILCDC) += tilcdc.o diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c index 3a3f4c6ed5e40..ab72b0a4619f7 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c @@ -43,6 +43,9 @@ struct tilcdc_crtc { int sync_lost_count; bool frame_intact; + + /* Only set if an external encoder is connected */ + bool simulate_vesa_sync; }; #define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base) @@ -248,6 +251,28 @@ static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + + if (!tilcdc_crtc->simulate_vesa_sync) + return true; + + /* + * tilcdc does not generate VESA-compliant sync but aligns + * VS on the second edge of HS instead of first edge. + * We use adjusted_mode, to fixup sync by aligning both rising + * edges and add HSKEW offset to fix the sync. + */ + adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; + adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) { + adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; + } else { + adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; + adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; + } + return true; } @@ -588,6 +613,14 @@ void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc, tilcdc_crtc->info = info; } +void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc, + bool simulate_vesa_sync) +{ + struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); + + tilcdc_crtc->simulate_vesa_sync = simulate_vesa_sync; +} + void tilcdc_crtc_update_clk(struct drm_crtc *crtc) { struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc); @@ -695,7 +728,7 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc) } if (stat & LCDC_SYNC_LOST) { - dev_err_ratelimited(dev->dev, "%s(0x%08x): Sync lost", + dev_info_ratelimited(dev->dev, "%s(0x%08x): Sync lost", __func__, stat); tilcdc_crtc->frame_intact = false; if (tilcdc_crtc->sync_lost_count++ > SYNC_LOST_COUNT_LIMIT) { @@ -708,7 +741,7 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc) } if (stat & LCDC_FIFO_UNDERFLOW) - dev_err_ratelimited(dev->dev, "%s(0x%08x): FIFO underfow", + dev_info_ratelimited(dev->dev, "%s(0x%08x): FIFO underfow", __func__, stat); return IRQ_HANDLED; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c index 37164f1ddc7a9..2684bec4a50b4 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c @@ -19,16 +19,16 @@ #include #include +#include #include "tilcdc_drv.h" #include "tilcdc_regs.h" #include "tilcdc_tfp410.h" -#include "tilcdc_slave.h" #include "tilcdc_panel.h" +#include "tilcdc_external.h" #include "drm_fb_helper.h" static LIST_HEAD(module_list); -static bool slave_probing; void tilcdc_module_init(struct tilcdc_module *mod, const char *name, const struct tilcdc_module_ops *funcs) @@ -44,11 +44,6 @@ void tilcdc_module_cleanup(struct tilcdc_module *mod) list_del(&mod->list); } -void tilcdc_slave_probedefer(bool defered) -{ - slave_probing = defered; -} - static struct of_device_id tilcdc_of_match[]; static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev, @@ -82,13 +77,6 @@ static int modeset_init(struct drm_device *dev) mod->funcs->modeset_init(mod, dev); } - if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) { - /* oh nos! */ - dev_err(dev->dev, "no encoders/connectors found\n"); - drm_mode_config_cleanup(dev); - return -ENXIO; - } - dev->mode_config.min_width = 0; dev->mode_config.min_height = 0; dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc); @@ -125,6 +113,8 @@ static int tilcdc_unload(struct drm_device *dev) tilcdc_crtc_dpms(priv->crtc, DRM_MODE_DPMS_OFF); + tilcdc_remove_external_encoders(dev); + drm_fbdev_cma_fini(priv->fbdev); drm_kms_helper_poll_fini(dev); drm_mode_config_cleanup(dev); @@ -182,6 +172,9 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) dev->dev_private = priv; + priv->is_componentized = + tilcdc_get_external_components(dev->dev, NULL) > 0; + priv->wq = alloc_ordered_workqueue("tilcdc", 0); if (!priv->wq) { ret = -ENOMEM; @@ -272,10 +265,28 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) goto fail_cpufreq_unregister; } + platform_set_drvdata(pdev, dev); + + if (priv->is_componentized) { + ret = component_bind_all(dev->dev, dev); + if (ret < 0) + goto fail_mode_config_cleanup; + + ret = tilcdc_add_external_encoders(dev, &bpp); + if (ret < 0) + goto fail_component_cleanup; + } + + if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) { + dev_err(dev->dev, "no encoders/connectors found\n"); + ret = -ENXIO; + goto fail_external_cleanup; + } + ret = drm_vblank_init(dev, 1); if (ret < 0) { dev_err(dev->dev, "failed to initialize vblank\n"); - goto fail_mode_config_cleanup; + goto fail_external_cleanup; } pm_runtime_get_sync(dev->dev); @@ -286,9 +297,6 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) goto fail_vblank_cleanup; } - platform_set_drvdata(pdev, dev); - - list_for_each_entry(mod, &module_list, list) { DBG("%s: preferred_bpp: %d", mod->name, mod->preferred_bpp); bpp = mod->preferred_bpp; @@ -319,6 +327,13 @@ static int tilcdc_load(struct drm_device *dev, unsigned long flags) fail_mode_config_cleanup: drm_mode_config_cleanup(dev); +fail_component_cleanup: + if (priv->is_componentized) + component_unbind_all(dev->dev, dev); + +fail_external_cleanup: + tilcdc_remove_external_encoders(dev); + fail_cpufreq_unregister: pm_runtime_disable(dev->dev); #ifdef CONFIG_CPU_FREQ @@ -652,24 +667,56 @@ static const struct dev_pm_ops tilcdc_pm_ops = { * Platform driver: */ +static int tilcdc_bind(struct device *dev) +{ + return drm_platform_init(&tilcdc_driver, to_platform_device(dev)); +} + +static void tilcdc_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops tilcdc_comp_ops = { + .bind = tilcdc_bind, + .unbind = tilcdc_unbind, +}; + static int tilcdc_pdev_probe(struct platform_device *pdev) { + struct component_match *match = NULL; + int ret; + /* bail out early if no DT data: */ if (!pdev->dev.of_node) { dev_err(&pdev->dev, "device-tree data is missing\n"); return -ENXIO; } - /* defer probing if slave is in deferred probing */ - if (slave_probing == true) - return -EPROBE_DEFER; - - return drm_platform_init(&tilcdc_driver, pdev); + ret = tilcdc_get_external_components(&pdev->dev, &match); + if (ret < 0) + return ret; + else if (ret == 0) + return drm_platform_init(&tilcdc_driver, pdev); + else + return component_master_add_with_match(&pdev->dev, + &tilcdc_comp_ops, + match); } static int tilcdc_pdev_remove(struct platform_device *pdev) { - drm_put_dev(platform_get_drvdata(pdev)); + struct drm_device *ddev = dev_get_drvdata(&pdev->dev); + struct tilcdc_drm_private *priv = ddev->dev_private; + + /* Check if a subcomponent has already triggered the unloading. */ + if (!priv) + return 0; + + if (priv->is_componentized) + component_master_del(&pdev->dev, &tilcdc_comp_ops); + else + drm_put_dev(platform_get_drvdata(pdev)); return 0; } @@ -694,7 +741,6 @@ static int __init tilcdc_drm_init(void) { DBG("init"); tilcdc_tfp410_init(); - tilcdc_slave_init(); tilcdc_panel_init(); return platform_driver_register(&tilcdc_platform_driver); } @@ -704,7 +750,6 @@ static void __exit tilcdc_drm_fini(void) DBG("fini"); platform_driver_unregister(&tilcdc_platform_driver); tilcdc_panel_fini(); - tilcdc_slave_fini(); tilcdc_tfp410_fini(); } diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h index 4c4aa4e1bae9f..80badad833f88 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h @@ -85,6 +85,9 @@ struct tilcdc_drm_private { unsigned int num_connectors; struct drm_connector *connectors[8]; + const struct drm_connector_helper_funcs *connector_funcs[8]; + + bool is_componentized; }; /* Sub-module for display. Since we don't know at compile time what panels @@ -116,7 +119,6 @@ struct tilcdc_module { void tilcdc_module_init(struct tilcdc_module *mod, const char *name, const struct tilcdc_module_ops *funcs); void tilcdc_module_cleanup(struct tilcdc_module *mod); -void tilcdc_slave_probedefer(bool defered); /* Panel config that needs to be set in the crtc, but is not coming from * the mode timings. The display module is expected to call @@ -166,6 +168,8 @@ irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc); void tilcdc_crtc_update_clk(struct drm_crtc *crtc); void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc, const struct tilcdc_panel_info *info); +void tilcdc_crtc_set_simulate_vesa_sync(struct drm_crtc *crtc, + bool simulate_vesa_sync); int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode); int tilcdc_crtc_max_width(struct drm_crtc *crtc); void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_external.c b/drivers/gpu/drm/tilcdc/tilcdc_external.c new file mode 100644 index 0000000000000..03acb4f99982f --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + * + * 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 "tilcdc_drv.h" +#include "tilcdc_external.h" + +static const struct tilcdc_panel_info panel_info_tda998x = { + .ac_bias = 255, + .ac_bias_intrpt = 0, + .dma_burst_sz = 16, + .bpp = 16, + .fdd = 0x80, + .tft_alt_mode = 0, + .invert_pxl_clk = 1, + .sync_edge = 1, + .sync_ctrl = 1, + .raster_order = 0, +}; + +static int tilcdc_external_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct tilcdc_drm_private *priv = connector->dev->dev_private; + int ret, i; + + ret = tilcdc_crtc_mode_valid(priv->crtc, mode); + if (ret != MODE_OK) + return ret; + + for (i = 0; i < priv->num_connectors && + priv->connectors[i] != connector; i++) + ; + + BUG_ON(priv->connectors[i] != connector); + BUG_ON(!priv->connector_funcs[i]); + + /* If the connector has its own mode_valid call it. */ + if (!IS_ERR(priv->connector_funcs[i]) && + priv->connector_funcs[i]->mode_valid) + return priv->connector_funcs[i]->mode_valid(connector, mode); + + return MODE_OK; +} + +static int tilcdc_add_external_encoder(struct drm_device *dev, int *bpp, + struct drm_connector *connector) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + struct drm_connector_helper_funcs *connector_funcs; + + priv->connectors[priv->num_connectors] = connector; + priv->encoders[priv->num_encoders++] = connector->encoder; + + /* Only tda998x is supported at the moment. */ + tilcdc_crtc_set_simulate_vesa_sync(priv->crtc, true); + tilcdc_crtc_set_panel_info(priv->crtc, &panel_info_tda998x); + *bpp = panel_info_tda998x.bpp; + + connector_funcs = devm_kzalloc(dev->dev, sizeof(*connector_funcs), + GFP_KERNEL); + if (!connector_funcs) + return -ENOMEM; + + /* connector->helper_private contains always struct + * connector_helper_funcs pointer. For tilcdc crtc to have a + * say if a specific mode is Ok, we need to install our own + * helper functions. In our helper functions we copy + * everything else but use our own mode_valid() (above). + */ + if (connector->helper_private) { + priv->connector_funcs[priv->num_connectors] = + connector->helper_private; + *connector_funcs = *priv->connector_funcs[priv->num_connectors]; + } else { + priv->connector_funcs[priv->num_connectors] = ERR_PTR(-ENOENT); + } + connector_funcs->mode_valid = tilcdc_external_mode_valid; + drm_connector_helper_add(connector, connector_funcs); + priv->num_connectors++; + + dev_dbg(dev->dev, "External encoder '%s' connected\n", + connector->encoder->name); + + return 0; +} + +int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + struct drm_connector *connector; + int num_internal_connectors = priv->num_connectors; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + bool found = false; + int i, ret; + + for (i = 0; i < num_internal_connectors; i++) + if (connector == priv->connectors[i]) + found = true; + if (!found) { + ret = tilcdc_add_external_encoder(dev, bpp, connector); + if (ret) + return ret; + } + } + return 0; +} + +void tilcdc_remove_external_encoders(struct drm_device *dev) +{ + struct tilcdc_drm_private *priv = dev->dev_private; + int i; + + /* Restore the original helper functions, if any. */ + for (i = 0; i < priv->num_connectors; i++) + if (IS_ERR(priv->connector_funcs[i])) + drm_connector_helper_add(priv->connectors[i], NULL); + else if (priv->connector_funcs[i]) + drm_connector_helper_add(priv->connectors[i], + priv->connector_funcs[i]); +} + +static int dev_match_of(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +int tilcdc_get_external_components(struct device *dev, + struct component_match **match) +{ + struct device_node *ep = NULL; + int count = 0; + + while ((ep = of_graph_get_next_endpoint(dev->of_node, ep))) { + struct device_node *node; + + node = of_graph_get_remote_port_parent(ep); + if (!node && !of_device_is_available(node)) { + of_node_put(node); + continue; + } + + dev_dbg(dev, "Subdevice node '%s' found\n", node->name); + if (match) + component_match_add(dev, match, dev_match_of, node); + of_node_put(node); + count++; + } + + if (count > 1) { + dev_err(dev, "Only one external encoder is supported\n"); + return -EINVAL; + } + + return count; +} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_external.h similarity index 63% rename from drivers/gpu/drm/tilcdc/tilcdc_slave.h rename to drivers/gpu/drm/tilcdc/tilcdc_external.h index 2f85048483209..6aabe2788760f 100644 --- a/drivers/gpu/drm/tilcdc/tilcdc_slave.h +++ b/drivers/gpu/drm/tilcdc/tilcdc_external.h @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Texas Instruments - * Author: Rob Clark + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha * * 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 @@ -15,12 +15,11 @@ * this program. If not, see . */ -#ifndef __TILCDC_SLAVE_H__ -#define __TILCDC_SLAVE_H__ - -/* sub-module for i2c slave encoder output */ - -int tilcdc_slave_init(void); -void tilcdc_slave_fini(void); +#ifndef __TILCDC_EXTERNAL_H__ +#define __TILCDC_EXTERNAL_H__ +int tilcdc_add_external_encoders(struct drm_device *dev, int *bpp); +void tilcdc_remove_external_encoders(struct drm_device *dev); +int tilcdc_get_external_components(struct device *dev, + struct component_match **match); #endif /* __TILCDC_SLAVE_H__ */ diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c deleted file mode 100644 index 8feb452803c90..0000000000000 --- a/drivers/gpu/drm/tilcdc/tilcdc_slave.c +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2012 Texas Instruments - * 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 -#include -#include -#include - -#include "tilcdc_drv.h" - -struct slave_module { - struct tilcdc_module base; - struct i2c_adapter *i2c; -}; -#define to_slave_module(x) container_of(x, struct slave_module, base) - -static const struct tilcdc_panel_info slave_info = { - .bpp = 16, - .ac_bias = 255, - .ac_bias_intrpt = 0, - .dma_burst_sz = 16, - .fdd = 0x80, - .tft_alt_mode = 0, - .invert_pxl_clk = 1, - .sync_edge = 1, - .sync_ctrl = 1, - .raster_order = 0, -}; - - -/* - * Encoder: - */ - -struct slave_encoder { - struct drm_encoder_slave base; - struct slave_module *mod; -}; -#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base) - -static inline struct drm_encoder_slave_funcs * -get_slave_funcs(struct drm_encoder *enc) -{ - return to_encoder_slave(enc)->slave_funcs; -} - -static void slave_encoder_destroy(struct drm_encoder *encoder) -{ - struct slave_encoder *slave_encoder = to_slave_encoder(encoder); - if (get_slave_funcs(encoder)) - get_slave_funcs(encoder)->destroy(encoder); - drm_encoder_cleanup(encoder); - kfree(slave_encoder); -} - -static void slave_encoder_prepare(struct drm_encoder *encoder) -{ - drm_i2c_encoder_prepare(encoder); - tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info); -} - -static bool slave_encoder_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ - /* - * tilcdc does not generate VESA-complient sync but aligns - * VS on the second edge of HS instead of first edge. - * We use adjusted_mode, to fixup sync by aligning both rising - * edges and add HSKEW offset to let the slave encoder fix it up. - */ - adjusted_mode->hskew = mode->hsync_end - mode->hsync_start; - adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW; - - if (mode->flags & DRM_MODE_FLAG_NHSYNC) { - adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC; - adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC; - } else { - adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC; - adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC; - } - - return drm_i2c_encoder_mode_fixup(encoder, mode, adjusted_mode); -} - - -static const struct drm_encoder_funcs slave_encoder_funcs = { - .destroy = slave_encoder_destroy, -}; - -static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = { - .dpms = drm_i2c_encoder_dpms, - .mode_fixup = slave_encoder_fixup, - .prepare = slave_encoder_prepare, - .commit = drm_i2c_encoder_commit, - .mode_set = drm_i2c_encoder_mode_set, - .save = drm_i2c_encoder_save, - .restore = drm_i2c_encoder_restore, -}; - -static struct tda998x_encoder_params tda998x_pdata = { - .swap_b = 0x3, - .mirr_b = 0x0, - .swap_a = 0x2, - .mirr_a = 0x0, - .swap_d = 0x1, - .mirr_d = 0x0, - .swap_c = 0x0, - .mirr_c = 0x0, - .swap_f = 0x5, - .mirr_f = 0x0, - .swap_e = 0x4, - .mirr_e = 0x0, - .audio_cfg = 0x3, /* I2S mode */ - .audio_clk_cfg = 1, /* select clock pin */ - .audio_frame[1] = 1, /* channels - 1 */ - .audio_format = AFMT_I2S, - .audio_sample_rate = 48000, -}; - -static const struct i2c_board_info info = { - I2C_BOARD_INFO("tda998x", 0x70), - .platform_data = &tda998x_pdata, -}; - -static struct drm_encoder *slave_encoder_create(struct drm_device *dev, - struct slave_module *mod) -{ - struct slave_encoder *slave_encoder; - struct drm_encoder *encoder; - int ret; - - slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL); - if (!slave_encoder) { - dev_err(dev->dev, "allocation failed\n"); - return NULL; - } - - slave_encoder->mod = mod; - - encoder = &slave_encoder->base.base; - encoder->possible_crtcs = 1; - - ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs, - DRM_MODE_ENCODER_TMDS); - if (ret) - goto fail; - - drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs); - - ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info); - if (ret) - goto fail; - - return encoder; - -fail: - slave_encoder_destroy(encoder); - return NULL; -} - -/* - * Connector: - */ - -struct slave_connector { - struct drm_connector base; - - struct drm_encoder *encoder; /* our connected encoder */ - struct slave_module *mod; -}; -#define to_slave_connector(x) container_of(x, struct slave_connector, base) - -static void slave_connector_destroy(struct drm_connector *connector) -{ - struct slave_connector *slave_connector = to_slave_connector(connector); - drm_connector_unregister(connector); - drm_connector_cleanup(connector); - kfree(slave_connector); -} - -static enum drm_connector_status slave_connector_detect( - struct drm_connector *connector, - bool force) -{ - struct drm_encoder *encoder = to_slave_connector(connector)->encoder; - return get_slave_funcs(encoder)->detect(encoder, connector); -} - -static int slave_connector_get_modes(struct drm_connector *connector) -{ - struct drm_encoder *encoder = to_slave_connector(connector)->encoder; - return get_slave_funcs(encoder)->get_modes(encoder, connector); -} - -static int slave_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) -{ - struct drm_encoder *encoder = to_slave_connector(connector)->encoder; - struct tilcdc_drm_private *priv = connector->dev->dev_private; - int ret; - - ret = tilcdc_crtc_mode_valid(priv->crtc, mode); - if (ret != MODE_OK) - return ret; - - return get_slave_funcs(encoder)->mode_valid(encoder, mode); -} - -static struct drm_encoder *slave_connector_best_encoder( - struct drm_connector *connector) -{ - struct slave_connector *slave_connector = to_slave_connector(connector); - return slave_connector->encoder; -} - -static int slave_connector_set_property(struct drm_connector *connector, - struct drm_property *property, uint64_t value) -{ - struct drm_encoder *encoder = to_slave_connector(connector)->encoder; - return get_slave_funcs(encoder)->set_property(encoder, - connector, property, value); -} - -static const struct drm_connector_funcs slave_connector_funcs = { - .destroy = slave_connector_destroy, - .dpms = drm_helper_connector_dpms, - .detect = slave_connector_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .set_property = slave_connector_set_property, -}; - -static const struct drm_connector_helper_funcs slave_connector_helper_funcs = { - .get_modes = slave_connector_get_modes, - .mode_valid = slave_connector_mode_valid, - .best_encoder = slave_connector_best_encoder, -}; - -static struct drm_connector *slave_connector_create(struct drm_device *dev, - struct slave_module *mod, struct drm_encoder *encoder) -{ - struct slave_connector *slave_connector; - struct drm_connector *connector; - int ret; - - slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL); - if (!slave_connector) { - dev_err(dev->dev, "allocation failed\n"); - return NULL; - } - - slave_connector->encoder = encoder; - slave_connector->mod = mod; - - connector = &slave_connector->base; - - drm_connector_init(dev, connector, &slave_connector_funcs, - DRM_MODE_CONNECTOR_HDMIA); - drm_connector_helper_add(connector, &slave_connector_helper_funcs); - - connector->polled = DRM_CONNECTOR_POLL_CONNECT | - DRM_CONNECTOR_POLL_DISCONNECT; - - connector->interlace_allowed = 0; - connector->doublescan_allowed = 0; - - get_slave_funcs(encoder)->create_resources(encoder, connector); - - ret = drm_mode_connector_attach_encoder(connector, encoder); - if (ret) - goto fail; - - drm_connector_register(connector); - - return connector; - -fail: - slave_connector_destroy(connector); - return NULL; -} - -/* - * Module: - */ - -static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev) -{ - struct slave_module *slave_mod = to_slave_module(mod); - struct tilcdc_drm_private *priv = dev->dev_private; - struct drm_encoder *encoder; - struct drm_connector *connector; - - encoder = slave_encoder_create(dev, slave_mod); - if (!encoder) - return -ENOMEM; - - connector = slave_connector_create(dev, slave_mod, encoder); - if (!connector) - return -ENOMEM; - - priv->encoders[priv->num_encoders++] = encoder; - priv->connectors[priv->num_connectors++] = connector; - - return 0; -} - -static const struct tilcdc_module_ops slave_module_ops = { - .modeset_init = slave_modeset_init, -}; - -/* - * Device: - */ - -static struct of_device_id slave_of_match[]; - -static int slave_probe(struct platform_device *pdev) -{ - struct device_node *node = pdev->dev.of_node; - struct device_node *i2c_node; - struct slave_module *slave_mod; - struct tilcdc_module *mod; - struct pinctrl *pinctrl; - uint32_t i2c_phandle; - struct i2c_adapter *slavei2c; - int ret = -EINVAL; - - /* bail out early if no DT data: */ - if (!node) { - dev_err(&pdev->dev, "device-tree data is missing\n"); - return -ENXIO; - } - - /* Bail out early if i2c not specified */ - if (of_property_read_u32(node, "i2c", &i2c_phandle)) { - dev_err(&pdev->dev, "could not get i2c bus phandle\n"); - return ret; - } - - i2c_node = of_find_node_by_phandle(i2c_phandle); - if (!i2c_node) { - dev_err(&pdev->dev, "could not get i2c bus node\n"); - return ret; - } - - /* but defer the probe if it can't be initialized it might come later */ - slavei2c = of_find_i2c_adapter_by_node(i2c_node); - of_node_put(i2c_node); - - if (!slavei2c) { - ret = -EPROBE_DEFER; - tilcdc_slave_probedefer(true); - dev_err(&pdev->dev, "could not get i2c\n"); - return ret; - } - - slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL); - if (!slave_mod) { - ret = -ENOMEM; - goto fail_adapter; - } - - mod = &slave_mod->base; - pdev->dev.platform_data = mod; - - mod->preferred_bpp = slave_info.bpp; - - slave_mod->i2c = slavei2c; - - tilcdc_module_init(mod, "slave", &slave_module_ops); - - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) - dev_warn(&pdev->dev, "pins are not configured\n"); - - tilcdc_slave_probedefer(false); - - return 0; - -fail_adapter: - i2c_put_adapter(slavei2c); - return ret; -} - -static int slave_remove(struct platform_device *pdev) -{ - struct tilcdc_module *mod = dev_get_platdata(&pdev->dev); - struct slave_module *slave_mod = to_slave_module(mod); - - tilcdc_module_cleanup(mod); - kfree(slave_mod); - - return 0; -} - -static struct of_device_id slave_of_match[] = { - { .compatible = "ti,tilcdc,slave", }, - { }, -}; - -struct platform_driver slave_driver = { - .probe = slave_probe, - .remove = slave_remove, - .driver = { - .owner = THIS_MODULE, - .name = "slave", - .of_match_table = slave_of_match, - }, -}; - -int __init tilcdc_slave_init(void) -{ - return platform_driver_register(&slave_driver); -} - -void __exit tilcdc_slave_fini(void) -{ - platform_driver_unregister(&slave_driver); -} diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c new file mode 100644 index 0000000000000..106679bca6cb4 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + * + * 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. + * + */ + +/* + * To support the old "ti,tilcdc,slave" binding the binding has to be + * transformed to the new external encoder binding. + */ + +#include +#include +#include +#include +#include +#include + +#include "tilcdc_slave_compat.h" + +struct kfree_table { + int total; + int num; + void **table; +}; + +static int __init kfree_table_init(struct kfree_table *kft) +{ + kft->total = 32; + kft->num = 0; + kft->table = kmalloc(kft->total * sizeof(*kft->table), + GFP_KERNEL); + if (!kft->table) + return -ENOMEM; + + return 0; +} + +static int __init kfree_table_add(struct kfree_table *kft, void *p) +{ + if (kft->num == kft->total) { + void **old = kft->table; + + kft->total *= 2; + kft->table = krealloc(old, kft->total * sizeof(*kft->table), + GFP_KERNEL); + if (!kft->table) { + kft->table = old; + kfree(p); + return -ENOMEM; + } + } + kft->table[kft->num++] = p; + return 0; +} + +static void __init kfree_table_free(struct kfree_table *kft) +{ + int i; + + for (i = 0; i < kft->num; i++) + kfree(kft->table[i]); + + kfree(kft->table); +} + +static +struct property * __init tilcdc_prop_dup(const struct property *prop, + struct kfree_table *kft) +{ + struct property *nprop; + + nprop = kzalloc(sizeof(*nprop), GFP_KERNEL); + if (!nprop || kfree_table_add(kft, nprop)) + return NULL; + + nprop->name = kstrdup(prop->name, GFP_KERNEL); + if (!nprop->name || kfree_table_add(kft, nprop->name)) + return NULL; + + nprop->value = kmemdup(prop->value, prop->length, GFP_KERNEL); + if (!nprop->value || kfree_table_add(kft, nprop->value)) + return NULL; + + nprop->length = prop->length; + + return nprop; +} + +static void __init tilcdc_copy_props(struct device_node *from, + struct device_node *to, + const char * const props[], + struct kfree_table *kft) +{ + struct property *prop; + int i; + + for (i = 0; props[i]; i++) { + prop = of_find_property(from, props[i], NULL); + if (!prop) + continue; + + prop = tilcdc_prop_dup(prop, kft); + if (!prop) + continue; + + prop->next = to->properties; + to->properties = prop; + } +} + +static int __init tilcdc_prop_str_update(struct property *prop, + const char *str, + struct kfree_table *kft) +{ + prop->value = kstrdup(str, GFP_KERNEL); + if (kfree_table_add(kft, prop->value) || !prop->value) + return -ENOMEM; + prop->length = strlen(str)+1; + return 0; +} + +static void __init tilcdc_node_disable(struct device_node *node) +{ + struct property *prop; + + prop = kzalloc(sizeof(*prop), GFP_KERNEL); + if (!prop) + return; + + prop->name = "status"; + prop->value = "disabled"; + prop->length = strlen((char *)prop->value)+1; + + of_update_property(node, prop); +} + +struct device_node * __init tilcdc_get_overlay(struct kfree_table *kft) +{ + const int size = __dtb_tilcdc_slave_compat_end - + __dtb_tilcdc_slave_compat_begin; + static void *overlay_data; + struct device_node *overlay; + int ret; + + if (!size) { + pr_warn("%s: No overlay data\n", __func__); + return NULL; + } + + overlay_data = kmemdup(__dtb_tilcdc_slave_compat_begin, + size, GFP_KERNEL); + if (!overlay_data || kfree_table_add(kft, overlay_data)) + return NULL; + + of_fdt_unflatten_tree(overlay_data, &overlay); + if (!overlay) { + pr_warn("%s: Unfattening overlay tree failed\n", __func__); + return NULL; + } + + of_node_set_flag(overlay, OF_DETACHED); + ret = of_resolve_phandles(overlay); + if (ret) { + pr_err("%s: Failed to resolve phandles: %d\n", __func__, ret); + return NULL; + } + + return overlay; +} + +static const struct of_device_id tilcdc_slave_of_match[] __initconst = { + { .compatible = "ti,tilcdc,slave", }, + {}, +}; + +static const struct of_device_id tilcdc_of_match[] __initconst = { + { .compatible = "ti,am33xx-tilcdc", }, + {}, +}; + +static const struct of_device_id tilcdc_tda998x_of_match[] __initconst = { + { .compatible = "nxp,tda998x", }, + {}, +}; + +static const char * const tilcdc_slave_props[] __initconst = { + "pinctrl-names", + "pinctrl-0", + "pinctrl-1", + NULL +}; + +void __init tilcdc_convert_slave_node(void) +{ + struct device_node *slave = NULL, *lcdc = NULL; + struct device_node *i2c = NULL, *fragment = NULL; + struct device_node *overlay, *encoder; + struct property *prop; + /* For all memory needed for the overlay tree. This memory can + be freed after the overlay has been applied. */ + struct kfree_table kft; + int ret; + + if (kfree_table_init(&kft)) + goto out; + + lcdc = of_find_matching_node(NULL, tilcdc_of_match); + slave = of_find_matching_node(NULL, tilcdc_slave_of_match); + + if (!slave || !of_device_is_available(lcdc)) + goto out; + + i2c = of_parse_phandle(slave, "i2c", 0); + if (!i2c) { + pr_err("%s: Can't find i2c node trough phandle\n", __func__); + goto out; + } + + overlay = tilcdc_get_overlay(&kft); + if (!overlay) + goto out; + + encoder = of_find_matching_node(overlay, tilcdc_tda998x_of_match); + if (!encoder) { + pr_err("%s: Failed to find tda998x node\n", __func__); + goto out; + } + + tilcdc_copy_props(slave, encoder, tilcdc_slave_props, &kft); + + for_each_child_of_node(overlay, fragment) { + prop = of_find_property(fragment, "target-path", NULL); + if (!prop) + continue; + if (!strncmp("i2c", (char *)prop->value, prop->length)) + if (tilcdc_prop_str_update(prop, i2c->full_name, &kft)) + goto out; + if (!strncmp("lcdc", (char *)prop->value, prop->length)) + if (tilcdc_prop_str_update(prop, lcdc->full_name, &kft)) + goto out; + } + + tilcdc_node_disable(slave); + + ret = of_overlay_create(overlay); + if (ret) + pr_err("%s: Creating overlay failed: %d\n", __func__, ret); + else + pr_info("%s: ti,tilcdc,slave node successfully converted\n", + __func__); +out: + kfree_table_free(&kft); + of_node_put(i2c); + of_node_put(slave); + of_node_put(lcdc); + of_node_put(fragment); +} + +int __init tilcdc_slave_compat_init(void) +{ + tilcdc_convert_slave_node(); + return 0; +} + +subsys_initcall(tilcdc_slave_compat_init); diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts new file mode 100644 index 0000000000000..693f8b0aea2da --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.dts @@ -0,0 +1,72 @@ +/* + * DTS overlay for converting ti,tilcdc,slave binding to new binding. + * + * Copyright (C) 2015 Texas Instruments Inc. + * Author: Jyri Sarha + * + * 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. + */ + +/* + * target-path property values are simple tags that are replaced with + * correct values in tildcdc_slave_compat.c. Some properties are also + * copied over from the ti,tilcdc,slave node. + */ + +/dts-v1/; +/ { + fragment@0 { + target-path = "i2c"; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + tda19988 { + compatible = "nxp,tda998x"; + reg = <0x70>; + status = "okay"; + + port { + hdmi_0: endpoint@0 { + remote-endpoint = <&lcd_0>; + }; + }; + }; + }; + }; + + fragment@1 { + target-path = "lcdc"; + __overlay__ { + port { + lcd_0: endpoint@0 { + remote-endpoint = <&hdmi_0>; + }; + }; + }; + }; + + __local_fixups__ { + fragment@0 { + __overlay__ { + tda19988 { + port { + endpoint@0 { + remote-endpoint = <0>; + }; + }; + }; + }; + }; + fragment@1 { + __overlay__ { + port { + endpoint@0 { + remote-endpoint = <0>; + }; + }; + }; + }; + }; +}; diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h new file mode 100644 index 0000000000000..403d35d87d0b4 --- /dev/null +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave_compat.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Texas Instruments + * Author: Jyri Sarha + * + * 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 . + */ +/* This header declares the symbols defined in tilcdc_slave_compat.dts */ + +#ifndef __TILCDC_SLAVE_COMPAT_H__ +#define __TILCDC_SLAVE_COMPAT_H__ + +extern uint8_t __dtb_tilcdc_slave_compat_begin[]; +extern uint8_t __dtb_tilcdc_slave_compat_end[]; + +#endif /* __TILCDC_SLAVE_COMPAT_H__ */ diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index 7bcbf863656ec..aa9bd47b03b5f 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -1091,7 +1091,7 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf, vgadev = vgadev_find(pdev); pr_debug("vgaarb: vgadev %p\n", vgadev); if (vgadev == NULL) { - pr_err("vgaarb: this pci device is not a vga device\n"); + pr_info("vgaarb: this pci device is not a vga device\n"); pci_dev_put(pdev); ret_val = -ENODEV; goto done; diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 9a25e51116b94..9538c9bd0a885 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -1005,6 +1005,8 @@ EXPORT_SYMBOL_GPL(i2c_new_device); */ void i2c_unregister_device(struct i2c_client *client) { + if (client->dev.of_node) + of_node_clear_flag(client->dev.of_node, OF_POPULATED); device_unregister(&client->dev); } EXPORT_SYMBOL_GPL(i2c_unregister_device); @@ -1313,8 +1315,11 @@ static void of_i2c_register_devices(struct i2c_adapter *adap) dev_dbg(&adap->dev, "of_i2c: walking child nodes\n"); - for_each_available_child_of_node(adap->dev.of_node, node) + for_each_available_child_of_node(adap->dev.of_node, node) { + if (of_node_test_and_set_flag(node, OF_POPULATED)) + continue; of_i2c_register_device(adap, node); + } } static int of_dev_node_match(struct device *dev, void *data) @@ -1846,6 +1851,11 @@ static int of_i2c_notify(struct notifier_block *nb, unsigned long action, if (adap == NULL) return NOTIFY_OK; /* not for us */ + if (of_node_test_and_set_flag(rd->dn, OF_POPULATED)) { + put_device(&adap->dev); + return NOTIFY_OK; + } + client = of_i2c_register_device(adap, rd->dn); put_device(&adap->dev); @@ -1856,6 +1866,10 @@ static int of_i2c_notify(struct notifier_block *nb, unsigned long action, } break; case OF_RECONFIG_CHANGE_REMOVE: + /* already depopulated? */ + if (!of_node_check_flag(rd->dn, OF_POPULATED)) + return NOTIFY_OK; + /* find our device by node */ client = of_find_i2c_device_by_node(rd->dn); if (client == NULL) diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 5e610f7de5aa2..0d1116dda1fac 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -37,6 +37,7 @@ config KMX61 be called kmx61. source "drivers/iio/imu/inv_mpu6050/Kconfig" +source "drivers/iio/imu/inv_mpu9250/Kconfig" endmenu @@ -50,4 +51,4 @@ config IIO_ADIS_LIB_BUFFER select IIO_TRIGGERED_BUFFER help A set of buffer helper functions for the Analog Devices ADIS* device - family. + family. \ No newline at end of file diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index e1e6e3d70e26d..d6987f5ee5a59 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -14,5 +14,6 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o obj-y += inv_mpu6050/ +obj-y += inv_mpu9250/ obj-$(CONFIG_KMX61) += kmx61.o diff --git a/drivers/iio/imu/inv_mpu9250/Kconfig b/drivers/iio/imu/inv_mpu9250/Kconfig new file mode 100644 index 0000000000000..21cc237cc2a2b --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/Kconfig @@ -0,0 +1,24 @@ +# +# inv-mpu-iio driver for Invensense MPU devices and combos +# + +config INV_MPU_IIO + tristate "Invensense MPU devices" + depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER && !INV_MPU + default n + help + This driver supports the Invensense MPU devices. + This includes MPU6050/MPU3050/MPU9150/ITG3500/MPU6500/MPU9250. + This driver can be built as a module. The module will be called + inv-mpu-iio. + +config INV_IIO_MPU3050_ACCEL_SLAVE_BMA250 + bool "Invensense MPU3050 slave accelerometer device for bma250" + depends on INV_MPU_IIO + default n + help + This is slave device enable MPU3050 accelerometer slave device. + Right now, it is only bma250. For other acceleromter device, + it can be added to this menu if the proper interface is filled. + There are some interface function to be defined. + diff --git a/drivers/iio/imu/inv_mpu9250/Kconfig.txt b/drivers/iio/imu/inv_mpu9250/Kconfig.txt new file mode 100644 index 0000000000000..a19f9809754e4 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/Kconfig.txt @@ -0,0 +1,12 @@ +# +# inv-mpu drivers for Invensense MPU devices and combos +# + +config INV_MPU_IIO + tristate "Invensense MPU devices" + depends on I2C && SYSFS && IIO && IIO_KFIFO_BUF && IIO_TRIGGER && !INV_MPU + default n + help + This driver supports the Invensense MPU devices. + This driver can be built as a module. The module will be called + inv-mpu-iio. diff --git a/drivers/iio/imu/inv_mpu9250/Makefile b/drivers/iio/imu/inv_mpu9250/Makefile new file mode 100644 index 0000000000000..0d79f59afef34 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for Invensense inv-mpu-iio device. +# + +obj-$(CONFIG_INV_MPU_IIO) += inv-mpu-iio.o + +inv-mpu-iio-objs := inv_mpu_core.o +inv-mpu-iio-objs += inv_mpu_ring.o +inv-mpu-iio-objs += inv_mpu_trigger.o +inv-mpu-iio-objs += inv_mpu_misc.o +inv-mpu-iio-objs += inv_mpu3050_iio.o +inv-mpu-iio-objs += dmpDefaultMPU6050.o +inv-mpu-iio-objs += inv_slave_compass.o +inv-mpu-iio-objs += inv_slave_pressure.o + +CFLAGS_inv_mpu_core.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_ring.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_trigger.o += -Idrivers/staging/iio +CFLAGS_inv_mpu_misc.o += -Idrivers/staging/iio +CFLAGS_inv_mpu3050_iio.o += -Idrivers/staging/iio +CFLAGS_dmpDefaultMPU6050.o += -Idrivers/staging/iio +CFLAGS_inv_slave_compass.o += -Idrivers/staging/iio +CFLAGS_inv_slave_pressure.o += -Idrivers/staging/iio + +# the Bosch BMA250 driver is added to the inv-mpu device driver because it +# must be connected to an MPU3050 device on the secondary slave bus. +ifeq ($(CONFIG_INV_IIO_MPU3050_ACCEL_SLAVE_BMA250), y) +inv-mpu-iio-objs += inv_slave_bma250.o +CFLAGS_inv_slave_bma250.o += -Idrivers/staging/iio +endif + diff --git a/drivers/iio/imu/inv_mpu9250/Makefile.txt b/drivers/iio/imu/inv_mpu9250/Makefile.txt new file mode 100644 index 0000000000000..0efcc6d706547 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/Makefile.txt @@ -0,0 +1,17 @@ +# +# Makefile for Invensense inv-mpu-iio device. +# + +obj-$(CONFIG_INV_MPU_IIO) += inv-mpu-iio.o + +inv-mpu-iio-objs := inv_mpu_core.o +inv-mpu-iio-objs += inv_mpu_ring.o +inv-mpu-iio-objs += inv_mpu_trigger.o +inv-mpu-iio-objs += inv_mpu_misc.o +inv-mpu-iio-objs += inv_mpu3050_iio.o +inv-mpu-iio-objs += dmpDefaultMPU6050.o + +# the Bosch BMA250 driver is added to the inv-mpu device driver because it +# must be connected to an MPU3050 device on the secondary slave bus. +inv-mpu-iio-objs += inv_slave_bma250.o + diff --git a/drivers/iio/imu/inv_mpu9250/README b/drivers/iio/imu/inv_mpu9250/README new file mode 100644 index 0000000000000..87f07e34f07e5 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/README @@ -0,0 +1,631 @@ +Kernel driver inv-mpu-iio +Author: Invensense + +Table of Contents: +================== +- Description +- Integrating the Driver in the Linux Kernel +- Board and Platform Data + > Interrupt Pin + > Platform Data +- Board File Modifications for Secondary I2C Configuration + > MPU-6050 + AKM8963 on the secondary I2C interface + > MPU-6500 + AKM8963 on the secondary I2C interface + > MPU-6515 + AKM8975 and BMP280 on the secondary I2C interface + > MPU-9150 + > MPU-9250 + > MPU-3050 + BMA250 on the secondary I2C interface +- Board File Modifications for Invensense Devices + > MPU-3050 + > ITG-3500 + > MPU-6050 + > MPU-6515 + > MPU-6500 + > MPU-6XXX + > MPU-9150 + > MPU-9250 +- IIO Subsystem + > Communicating with the Driver in Userspace + > ITG-3500 + > MPU-6050 and MPU-6500 + > MPU-9150 + > MPU-9250 + > MPU-3050 + BMA250 on the secondary I2C interface +- Suspend and Resume +- DMP Event +- Motion Event +- Streaming Data to an Userspace Application +- Recommended Sysfs Entry Setup Sequence + > With DMP Firmware + > Without DMP Firmware +- Test Applications + > Running Test Applications with MPU-9150/MPU-6050/MPU-6500/MPU-9250 + > Running Test Applications with MPU-3050/ITG-3500 + + +Description +=========== +This document describes how to install the Invensense device driver into a +Linux kernel. The Invensense driver currently supports the following sensors: +- ITG-3500 +- MPU-6050 +- MPU-9150 +- MPU-6500 +- MPU-9250 +- MPU-3050 +- MPU-6XXX(either MPU6050 or MPU6500, driver to do auto detection) + +The slave address of each device is either 0x68 or 0x69, depending on the AD0 +pin value of the device. Please refer to the appropriate product specification +document for further information regarding the AD0 pin. The driver supports both +addresses. + +The following files are included in this package: +- Kconfig +- Makefile +- inv_mpu_core.c +- inv_mpu_misc.c +- inv_mpu_trigger.c +- inv_mpu3050_iio.c +- inv_mpu_iio.h +- inv_mpu_ring.c +- inv_slave_bma250.c +- inv_slave_compass.c +- dmpDefaultMPU6050.c +- dmpkey.h +- dmpmap.h +- mpu.h + + +Integrating the Driver in the Linux Kernel +========================================== +Please add the files as follows: +- Add mpu.h to "kernel/include/linux". +- Add all other files to drivers/staging/iio/imu/inv_mpu +(another directory is acceptable, but this is the recommended destination) + +In order to see the driver in menuconfig when building the kernel, please +make modifications as shown below: + + modify "drivers/staging/iio/imu/Kconfig" with: + >> source "drivers/staging/iio/imu/inv_mpu/Kconfig" + + modify "drivers/staging/iio/imu/Makefile" with: + >> obj-y += inv_mpu/ + + +Board and Platform Data +======================= +In order to recognize the Invensense device on the I2C bus, the board file must +be modified. +The i2c_board_info instance must be defined as shown below. + +Interrupt Pin +------------- +The hardcoded value of 140 corresponds to the GPIO input pin connected to the +Invensense device's interrupt pin. +This pin will most likely be different for your platform, and the value should +be changed accordingly. + +Platform Data +------------- +The platform data (orientation matrix and secondary bus configurations) must be +modified as show below, according to your particular platform configuration. + +Please note that the MPU-9150 it is treated as a MPU-6050 with AKM8975 on the +device's secondary I2C interface. Thus the secondary I2C address must be +provided. + +Please note that the MPU-9250 it is treated as a MPU-6500 with AKM8963 on the +device's secondary I2C interface. Thus the secondary I2C address must be +provided. + +Board File Modifications for Secondary I2C Configuration +======================================================== +For the Panda Board, the board file can be found at +arch/arm/mach-omap2/board-omap4panda.c. +Please modify the pertinent baord file in your system according to the examples +shown below: + +MPU-6050 + AKM8963 on the secondary I2C interface +------------------------------------------------- +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0E, + .secondary_orientation = { 0, 1, 0, + 1, 0, 0, + 0, 0, -1 }, +}; + +MPU-6500 + AKM8963 on the secondary I2C interface +------------------------------------------------- +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0E, + .secondary_orientation = { 0, 1, 0, + 1, 0, 0, + 0, 0, -1 }, +}; + +MPU-6515 + AK8975 + BMP280 on the secondary I2C interface +Note: sec_slave_XXX can only be compass or accelerometer in 3050 case. + aux_slave_XXX can only be pressure. +-------------------------------------------------- +static struct mpu_platform_data mpu_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8975, + .secondary_i2c_addr = 0x0E, + .secondary_orientation = { 0, 1, 0, + 1, 0, 0, + 0, 0, -1 }, + .aux_slave_type = SECONDARY_SLAVE_TYPE_PRESSURE, + .aux_slave_id = PRESSURE_ID_BMP280, + .aux_i2c_addr = 0x76, +}; +static struct i2c_board_info __initdata chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6515", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &mpu_data, + } +}; + +MPU-9150 +-------- +For MPU-9150, please provide the following secondary I2C bus information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8975, + .secondary_i2c_addr = 0x0C, + .secondary_orientation = { 0, 1, 0, + 1, 0, 0, + 0, 0, -1 }, +}; + +MPU-9250 +-------- +For MPU-9250, please provide the following secondary I2C bus information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0C, + .secondary_orientation = { 0, 1, 0, + -1, 0, 0, + 0, 0, 1 }, +}; + +MPU-3050 + BMA250 on the secondary I2C interface +------------------------------------------------ +For BMA250 on the secondary I2C bus, please provide the following information. + +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x10, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_ACCEL, + .sec_slave_id = ACCEL_ID_BMA250, + .secondary_i2c_addr = 0x18, +}; + + +Board File Modifications for Invensense Devices +=============================================== +For Invensense devices, please provide the i2c init data as shown in the +examples below. + +In the _i2c_init function, the device is registered in the following manner: + + // arch/arm/mach-omap2/board-omap4panda.c + // in static int __init omap4_panda_i2c_init(void) + omap_register_i2c_bus(4, 400, + single_chip_board_info, + ARRAY_SIZE(single_chip_board_info)); + +MPU-3050 +-------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu3050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +ITG-3500 +-------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("itg3500", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU6050 +------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU6500 +------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6500", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU6XXX +------- +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6xxx", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU9150 +------- +arch/arm/mach-omap2/board-omap4panda.c +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu9150", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +MPU9250 +------- +arch/arm/mach-omap2/board-omap4panda.c +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu9250", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +IIO subsystem +============= +A successful installation will create the following two new directories under +/sys/bus/iio/devices: + - iio:device0 + - trigger0 + +Also, a new file, "iio:device0", will be created in the /dev/ diretory. +(if you have more than one IIO device, the file will be named "iio:deviceX", +where X is a number) + + +Communicating with the Driver in Userspace +------------------------------------------ +The driver generates several files in sysfs upon installation. +These files are used to communicate with the driver. The files can be found +at /sys/bus/iio/devices/iio:device0 (or ../iio:deviceX as shown above). + +A brief description of the pertinent files for each Invensense device is shown +below: + +ITG-3500 +-------- +temperature (Read-only) +--Read temperature data directly from the temperature register. + +sampling_frequency (Read/write) +--Configure the ADC sampling rate and FIFO output rate. + +sampling_frequency_available(read-only) +--show commonly used frequency + +clock_source (Read-only) +--Check which clock-source is used by the chip. + +power_state (Read/write) +--turn on/off the power supply + +self_test (read-only) +--read this entry trigger self test. The return value is D. +D is the success/fail. +For different chip, the result is different for success/fail. +1 means success 0 means fail. The LSB of D is for gyro; the bit +next to LSB of D is for accel. The bit 2 of D is for compass result. + +key (read-only) +--show the key value of this driver. Used by MPL. + +gyro_matrix (read-only) +--show the orientation matrix obtained from the board file. + +MPU-6050 and MPU-6500 +--------------------- +MPU-6050 and MPU-6500 have all sysfs files belonging to ITG-3500 (shown above). +In addition, it has the files below: + +gyro_enable (read/write) +--enable/disable gyro functionality. Affects raw_gyro. Turning this off this + will shut down gyro and save power. + +accl_enable (read/write) +--enable/disable accelerometer functionality. Affects raw_accl. +Turning this off this will shut down accel and save power. + +firmware_loaded (read/write) +--Flag indicating the whether firmware is loaded or not in the DMP engine. +0 means no firmware loaded. 1 means firmware is already loaded . This +flag can only be written as 0. It internally updates to 1. + +dmp_on(read/write) +--This entry controls whether to run DMP or not. +Write 1 to enable DMP and write 0 to disable dmp. +Please note that firmware_loaded must be 1 in order to enable DMP. + +dmp_int_on(read/write) +--This entry controls whether dmp interrupt is on/off. +Please note that firmware_loaded must be 1. +Also, we'd like to remind you that it is sometimes advantageous to +turn interrupts off while the DMP is running. + +dmp_output_rate +--control dmp output rate when dmp is on. + +dmp_event_int_on(read/write) +--This entry controls whether dmp event interrupt is on/off. +Please note that turning this on will turn off the data interrupt. +Interrupts will be generated only when events occur. +This is useful for saving power when the system is waiting for a special event +to wake up. + +dmp_firmware (write only binary file) +--DMP firmware code is loaded into this entry. +If loading is successful, the firmware_loaded flag will be updated to 1. +In order to load new firmware, the firmware_loaded flag must be first set to 0. + +accel_matrix +--orientation matrix for accelerometer. + +quaternion_on +--Turn on/off quaterniion data output. DMP is required for this feature. + +pedometer_time +pedometer_steps, +--Pedometer related entries + +event_tap +event_display_orientation +event_accel_motion +event_smd +--Event related entries. +Please poll these entries to read their values. Direct reads will yield +meaningless results. +Further details are provided in the DMP Events section of this README. + +tap_on +--Controls tap function of DMP + +tap_time +tap_min_count +tap_threshold +--Tap related entries. Controls various parameters of tap function. + +display_orientation_on +--Turn on/off display orientation function of DMP. + +smd_enable +enable SMD(Significant Motion Detection) detection. + +smd_threshold +This set the threshold of the motion when SMD start to be triggered. The +value is in acclerometer counts. + +smd_delay_threshold +This sets the threshold of time after which SMD can be triggered. +The value is in seconds. + +smd_delay_threshold2 +This sets the threshold of time during which SMD can be triggered (after the +smd_delay_threshold timer has expired). +The value is in seconds. + +quaternion_on +--Turn on/off quaterniion data output. DMP is required for this feature. + +Low power accel motion interrupt related settings. +if motion_lpa_on is set, this will disable all engines except accel. Accel will +enter low power mode and the whole chip will be turned on/off at specific frequency. +----------------------------------------------------------------------------- +motion_lpa_duration +--set motion duration. in ms. This means filtered out all the motino interrupts + during this period. + +motion_lpa_threshold +--set motion threshold. in mg. The maximum is 1020mg and resolution is 32mg. + +motion_lpa_on +--turn on/off motion function. + +motion_lpa_freq +--motion lpa frequency. which determines power on/off frequency. +------------------------------------------------------------------------------ +MPU-9150 +-------- +MPU-9150 has all of MPU-6050's entries. It also has two additional entries, +described below. + +compass_enable (read/write) +--Enables compass function. + +compass_matrix (read-only) +--Compass orientation matrix + +MPU-3050 with BMA250 on secondary I2C interface +----------------------------------------------- +MPU-3050 with BMA250 on the secondary I2C interface has ever ITG-3500 entry. +It also has two additional entries, shown below: + +accl_matrix + +accl_enable + +Suspend and Resume +=================================================== +The suspend and resume functions are call backs registered to the system +and executed when the system goes in suspend and resumes. +It is enabled when CONFIG_PM is defined. +The current behavior is simple: +- suspend will turn off the chip +- resume will turn on the chip + +However, it is possible for the driver to do more complex things; +for example, leaving pedometers running when system is off. This can save whole +system power while letting pedometer working. Other behaviors are possible +too. + +DMP Event +========= +A DMP Event is an event that is output by the DMP unit within the Invensense +device (MPU). +Only the MPU-6050, MPU-6500, MPU-9250, MPU-9150, MPU-9250 feature the DMP. + +There are four sysfs entries for DMP events: +- event_tap +- event_display_orientation +- event_accel_motion +- event_smd + +These events must be polled before reading. + +The proper method to poll sysfs is as follows: +1. open file. +2. dummy read. +3. poll. +4. once the poll passed, use fopen and fread to read the sysfs entry. +5. interpret the data. + +Streaming Data to an Userspace Application +========================================== +When streaming data to an userspace application, we recommend that you access +gyro/accel/compass data via /dev/iio:device0. + +Please follow the steps below to read data at a constant rate from the driver: + +1. Write a 1 to power_state to turn on the chip. This is the default setting + after installing the driver. +2. Write the desired output rate to fifo_rate. +3. Write 1 to enable to turn on the event. +4. Read /dev/iio:device0 to get a string of gyro/accel/compass data. +5. Parse this string to obtain each gyro/accel/compass element. +6. If dmp firmware code is loaded, use "dmp_on" to enable/disable dmp. +7. If compass is enabled, the output will contain compass data. + + +Recommended Sysfs Entry Setup Senquence +======================================= + +Without DMP Firmware +-------------------- +1. Set "power_state" to 1, +2. Set the scale and fifo rate values according to your needs. +3. Set gyro_enable, accel_enable, and compass_enable according to your needs. + For example: + - If you only want gyro data, set accel_enable to 0 (and compass_enable to + 0, if applicable). + - If you only want accel data, set gyro_enable to 0 (and compass_enable to + 0, if applicable). + - If you only want compass data, set gyro_enable to 0 and accel_enable to 0. +4. Set "enable" to 1. +5. You will now get the output that you want. + +With DMP Firmware +----------------- +1. Set "power_state" to 1. +2. Write "0" to firmware_loaded if it is not zero already. +3. Load firmware into "dmp_firmware" as a whole. Don't split the DMP firmware + image. +4. Make sure firmware_loaded is 1 after loading the DMP image. +5. Make appropriate configurations as shown above in the "without DMP firmware" + case. +6. Set dmp_on to 1. +7. Set "enable" to 1. + +Please note that the enable function uses the enable entry under +"/sys/bus/iio/devices/iio:device0/buffer" + +Test Applications +================= +A test application is located under software/simple_apps/mpu_iio. +This application is stand-alone in that it cannot be run concurrently with other +entities trying to access the device node(s) or sysfs entries; in particular, +the + +Running Test Applications with MPU-9150/MPU-6050/MPU-6500/MPU-9250 +--------------------------------------------------------- +To run test applications with MPU-9150, MPU-9250, MPU-6050, or MPU-6500 devices, +please use the following commands: + +1. For tap/display orientation events: + mpu_iio -c 10 -l 3 -p + +2. In addition, to test the motion interrupt (and no_motion on MPU6050) use: + mpu_iio -c 10 -l 3 -p -m + +3. For printing data normally: + mpu_iio -c 10 -l 3 -r + +Running Test Applications with MPU-3050/ITG-3500 +------------------------------------------------ +To run test applications with MPU-3050 or ITG-3500 devices, +please use the following command: + +1. For printing data normally: + mpu_iio -c 10 -l 3 -r + +Please use mpu_iio.c and iio_utils.h as example code for your development +purposes. + +Stress test application +================================= +A stress test application is located under software/simple_apps/stress_iio. +This application simulates HAL's usage calls to the driver. It creates three +threads. One for data read; one for event read; one for sysfs control. +It can run without any parameters or run with some control parameters. Please +see README in the same directories for details. + diff --git a/drivers/iio/imu/inv_mpu9250/README.txt b/drivers/iio/imu/inv_mpu9250/README.txt new file mode 100644 index 0000000000000..a0cfb3f2b7b81 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/README.txt @@ -0,0 +1,324 @@ +Kernel driver inv-mpu-iio +Author: Invensense + +Description +----------- +This document describes how to install the Invensense device driver into a +Linux kernel. At the moment, this driver supports the ITG3500/MPU6050/MPU9150/MPU3050. The slave +address of these four chips are 0x68. However, the actual slave address depends on the board +configuration. The driver does not assume anything about it. + +Files included in this package: +Kconfig +Makefile +inv_mpu_core.c +inv_mpu_misc.c +inv_mpu_trigger.c +inv_mpu3050_iio.c +inv_mpu_iio.h +inv_mpu_ring.c +inv_slave_bma250.c +dmpDefaultMPU6050.c +dmpkey.h +dmpmap.h +mpu.h +Including the driver in the Linux kernel +---------------------------------------- +mpu.h should be added to "kernel/include/linux". +Other files listed should be added to the drivers/staging/iio/imu/mpu directory (or another +directory of your choosing). When building the kernel, the driver will not +appear in menuconfig without modifications similar to those below: + +modify "drivers/staging/iio/imu/Kconfig" like +source "drivers/staging/iio/imu/mpu/Kconfig" + +modify "drivers/staging/iio/imu/Makefile" +obj-y += mpu/ + +Board and Platform Data +----------------------- +The board file needs to be modified to register the device on an I2C bus. An +i2c_board_info instance must be defined as seen below. The hardcoded value of +140 corresponds to the GPIO input pin wired to the device's interrupt pin. +This pin will most likely be different for your platform. +platform data is for orientation matrix, and secondary bus situations. +For MPU9150, it regarded as a MPU9150 and AKM8975 in the secondary. +So the secondary i2c address must be filled. +----------------------------------------------------------------- +The board file is arch/arm/mach-omap2/board-omap4panda.c or +modify the board file in your system as below: +-------------------------------------------------------- +For AKM8963 in the secondary i2c bus of MPU6050, +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8963, + .secondary_i2c_addr = 0x0E +}; +----------------------------------------------------------- +For MPU9150, the secondary i2c bus address must be filled as below. +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS, + .sec_slave_id = COMPASS_ID_AK8975, + .secondary_i2c_addr = 0x0E +}; +----------------------------------------------------------- +for BMA250 in the secondary, please use the platform data as: +static struct mpu_platform_data gyro_platform_data = { + .int_config = 0x00, + .level_shifter = 0, + .orientation = { -1, 0, 0, + 0, 1, 0, + 0, 0, -1 }, + .sec_slave_type = SECONDARY_SLAVE_TYPE_ACCEL, + .sec_slave_id = ACCEL_ID_BMA250, + .secondary_i2c_addr = 0x18, +}; +--------------------------------------------------------------- +the i2c init data is: +---------------------------------------------------------------- +For MPU3050, +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu3050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; +---------------------------------------------------------------- +for ITG3500: +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("itg3500", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; +for MPU6050 +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu6050", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; +for MPU9150 +arch/arm/mach-omap2/board-omap4panda.c +static struct i2c_board_info __initdata single_chip_board_info[] = { + { + I2C_BOARD_INFO("mpu9150", 0x68), + .irq = (IH_GPIO_BASE + MPUIRQ_GPIO), + .platform_data = &gyro_platform_data, + }, +}; + +In the _i2c_init function, the device is registered in the following manner: + +arch/arm/mach-omap2/board-omap4panda.c + in static int __init omap4_panda_i2c_init(void) +omap_register_i2c_bus(4, 400, single_chip_board_info, ARRAY_SIZE(single_chip_board_info)); + +IIO subsystem +---------------------------------------------- +successful installation will create two directories under /sys/bus/iio/devices +iio:device0 +trigger0 +Under /dev/ diretory, a file "iio:device0" will also be created(or iio:deviceX, if +you have more than one iio devices). +Communicating with the driver in userspace +------------------------------------------ +Upon installation, the driver generates several files in sysfs. If your +platform is configured as detailed above, navigate to the following path to +find these files: +/sys/bus/iio/devices/iio:device0 + +The list below provides a brief description for each file. +-------------------------------------- +For ITG3500: +temperature (Read-only) +Read temperature data directly from the temperature register. + +sampling_frequency (Read/write) +Configure the ADC sampling rate and FIFO output rate. + +sampling_frequency_available(read-only) +show commonly used frequency + +clock_source (Read-only) +Check which clock-source is used by the chip. + +power_state (Read/write) +turn on/off the power supply + +self_test (read-only) +read this entry trigger self test. The return value is D. +D is the success/fail. +For different chip, the result is different for success/fail. +1 means success 0 means fail. The LSB of D is for gyro; the bit +next to LSB of D is for accel. The bit 2 of D is for compass result. + +key (read-only) +show the key value of this driver. Used by MPL. + +gyro_matrix (read-only) +show the orient matrix obtained from board file. + +------------------------------------------------------------- +For MPU6050: +MPU6050 has all the sysfs files that ITG3500 has. It has additional files list below: + +gyro_enable (read/write) +enable/disable gyro functionality. affect raw_gyro. turn off this will shut down gyro and save power. + +accl_enable (read/write) +enable/disable accelerometer functionality. affect raw_accl. turn off this will shut down accel and save power. + +firmware_loaded (read/write) +Flag indicate the whether firmware is loaded or not in the DMP engine. 0 means no firmware loaded. +1 means firmware is already loaded . This flag can only be written as 0. 1 is updated +internally. + +dmp_on(read/write) +This entry controls whether to run DMP or not. To enable DMP , firmware_loaded must be 1. write 1 to enable +DMP and write 0 to disable dmp. + +dmp_in_on(read/write) +This entry controls whether dmp interrupt is on/off. firmware_loaded must be 1. sometimes, it is desirable +that interrupt is off while DMP is running. + +dmp_firmware (write only binary file) +This is the entry that firmware code is loaded into. If the action is succeful, firmware_loaded will +be updated as 1. In order to load new firmware, firmware_loaded flag should be set 0. + +lpa_mode(read-write) +Low power accelerometer mode +lpa_freq(read-write) +low power acceleromter frequency. + +accel_matrix +orient matrix for accel + +flick_lower, +flick_upper, +flick_counter, +flick_message_on, +flick_int_on, +flick_axis, +Flick related entry + +pedometer_time +pedometer_steps, +Pedometer related entry + +event_flick +event_tap +event_orientation +event_display_orientation +event related entry + +tap_on +control tap function of DMP + +tap_time +tap_min_count +tap_threshold +tap related entries. control various parameters of tap function. + +orientation_on +turn on/off orientation function of DMP. + +display_orientation_on +turn on/off display orientation function of DMP. + +quaternion_on +turn on/off quaterniion data output. must use DMP. +------------------------------------------------------------------- +for MPU9150 and secondary compass +MPU9150 has every entry MPU6050 has. It has additional entries: + +compass_enable (read/write) +enable this will enable compass function. + +compass_matrix (read-only) +compass orient matrix +--------------------- +for MPU3050 and secondary accelerometer(only BMA250 is supported right now) +It has every entry ITG3500 has and has two addiontal entries. +accl_matrix +accl_enable +---------------------------------------------------------------------------------- +low power accelerometer mode +Lower power accelerometer mode is a special mode. It works only for accelerometer. +It has two entries, lpa_mode and lpa_freq. Only MPU6050 and MPU9150 has this mode. +To run low power accel mode, set lpa_mode to 1, set lpa_freq to 0~3, which corresponds +to 1.25Hz, 5Hz, 20Hz, 40Hz. "gyro_enable" and "compass_enable" must be zero. "dmp_on" +must be zero. +----------------------------------------------------------------------------------- +dmp event. +dmp event is event out by the DMP unit inside MPU. Only MPU6050 and MPU9150 supports this. +There are four sysfs entreis, event_flick, event_tap and event_orientation and +event_display_orientation. These four events must +be polled before read. The proper method to poll sysfs is: +1. open file. +2. dummy read. +3. poll. +4. once the poll passed, use fopen and fread to read the sysfs entry. +5. interpret the data. +------------------------------------------------------------------------------ +If streaming to a userspace application, the recommended way to access gyro/accel/compass +data is via /dev/iio:device0. Follow these steps to get constant readings from +the driver: + +1. Write a 1 to power_state to turn on the chip. This is the default setting + after installing the driver. +2. Write the desired output rate to fifo_rate. +3. write 1 to enable to turn on the event. +4. Read /dev/iio:device0 to get a string of gyro/accel/compass data. +5. Parse this string to obtain each gyro/accel/compass element. +6. If dmp firmware code is loaded, using "dmp_on" to enable/disable dmp . +7. If compass is enabled, output will have compass data. +=========================================================================== + Recommended sysfs entry setup senquence +1. without DMP firmware +1.1 set "power_state" to 1, +1.2 change scale and fifo rate value to your need. +1.3 change gyro_enable and accle_enable and compass_enable to your needs. For example, +if you want gyro only, set accl_enable to 0 or set accl_enable to zero and compass_enable to zero. +If you want accel only, set gyro_enable to 0 or set gyro_enable to zero and compass_enable to zero. +If you want compass only, disable gyro and accel. +1.4 set "enable" to 1. you will get output you want. + +2. With DMP firmware +2.1 set "power_state" to 1, +2.2 write "0" to firmware_loaded if it is not zero already. +2.3 load firmware into "dmp_firmware" as a whole. Don't split the DMP firmware image. +2.4 make sure firmware_loaded is 1 after loading. +2.5 make other configurations similar to the situation as without DMP firmware. +2.6 set dmp_on to 1. +2.7 set "enable" to 1. +======================================================= +The enable function is using enable entry under "/sys/bus/iio/devices/iio:device0/buffer" +========================================================== +test applications: +Test application is under ARTHROPOD/trunk/software/simple_apps/mpu_iio +------------------------------------------ +To run with MPU9150/MPU6050: +using the following command: +for orientation/tap/flick/display orientation event: +mpu_iio -c 10 -l 3 -p +for normal data print +mpu_iio -c 10 -l 3 +---------------------------------------- +To run with MPU3050/ITG3500: +mpu_iio -c 10 -l 3 -r +----------------------------------------- +Please use mpu_iio.c and iio_utils.h as the sample code for your development. diff --git a/drivers/iio/imu/inv_mpu9250/dmpDefaultMPU6050.c b/drivers/iio/imu/inv_mpu9250/dmpDefaultMPU6050.c new file mode 100644 index 0000000000000..a85acea3c1758 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/dmpDefaultMPU6050.c @@ -0,0 +1,367 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include "inv_mpu_iio.h" +#include "dmpKey.h" +#include "dmpmap.h" + +#define CFG_OUT_PRESS (2266) +#define CFG_OUT_QUAT (2095) +#define CFG_PED_ENABLE (2364) +#define CFG_OUT_GYRO (2198) +#define CFG_PEDSTEP_DET (2605) +#define OUT_GYRO_DAT (2207) +#define CFG_FIFO_INT (2362) +#define OUT_CPASS_DAT (2241) +#define CFG_AUTH (1176) +#define OUT_ACCL_DAT (2173) +#define FCFG_1 (1203) +#define FCFG_3 (1228) +#define FCFG_2 (1207) +#define CFG_OUT_CPASS (2232) +#define FCFG_7 (1214) +#define CFG_OUT_3QUAT (2060) +#define OUT_PRESS_DAT (2275) +#define OUT_QUAT_DAT (2105) +#define OUT_3QUAT_DAT (2070) +#define CFG_7 (1425) +#define OUT_PQUAT_DAT (2139) +#define CFG_PED_INT (2593) +#define SMD_TP2 (1390) +#define SMD_TP1 (1369) +#define CFG_MOTION_BIAS (1427) +#define CFG_OUT_ACCL (2164) +#define CFG_OUT_STEPDET (2030) +#define CFG_OUT_PQUAT (2130) + +#define D_0_22 (22+512) +#define D_0_24 (24+512) + +#define D_0_36 (36) +#define D_0_52 (52) +#define D_0_96 (96) +#define D_0_104 (104) +#define D_0_108 (108) +#define D_0_163 (163) +#define D_0_188 (188) +#define D_0_192 (192) +#define D_0_224 (224) +#define D_0_228 (228) +#define D_0_232 (232) +#define D_0_236 (236) + +#define D_1_2 (256 + 2) +#define D_1_4 (256 + 4) +#define D_1_8 (256 + 8) +#define D_1_10 (256 + 10) +#define D_1_24 (256 + 24) +#define D_1_28 (256 + 28) +#define D_1_36 (256 + 36) +#define D_1_40 (256 + 40) +#define D_1_44 (256 + 44) +#define D_1_72 (256 + 72) +#define D_1_74 (256 + 74) +#define D_1_79 (256 + 79) +#define D_1_88 (256 + 88) +#define D_1_90 (256 + 90) +#define D_1_92 (256 + 92) +#define D_1_96 (256 + 96) +#define D_1_98 (256 + 98) +#define D_1_106 (256 + 106) +#define D_1_108 (256 + 108) +#define D_1_112 (256 + 112) +#define D_1_128 (256 + 144) +#define D_1_152 (256 + 12) +#define D_1_160 (256 + 160) +#define D_1_176 (256 + 176) +#define D_1_178 (256 + 178) +#define D_1_218 (256 + 218) +#define D_1_232 (256 + 232) +#define D_1_236 (256 + 236) +#define D_1_240 (256 + 240) +#define D_1_244 (256 + 244) +#define D_1_250 (256 + 250) +#define D_1_252 (256 + 252) +#define D_2_12 (512 + 12) +#define D_2_96 (512 + 96) +#define D_2_108 (512 + 108) +#define D_2_208 (512 + 208) +#define D_2_224 (512 + 224) +#define D_2_236 (512 + 236) +#define D_2_244 (512 + 244) +#define D_2_248 (512 + 248) +#define D_2_252 (512 + 252) +#define CPASS_BIAS_X (30 * 16 + 4) +#define CPASS_BIAS_Y (30 * 16 + 8) +#define CPASS_BIAS_Z (30 * 16 + 12) +#define CPASS_MTX_00 (32 * 16 + 4) +#define CPASS_MTX_01 (32 * 16 + 8) +#define CPASS_MTX_02 (36 * 16 + 12) +#define CPASS_MTX_10 (33 * 16) +#define CPASS_MTX_11 (33 * 16 + 4) +#define CPASS_MTX_12 (33 * 16 + 8) +#define CPASS_MTX_20 (33 * 16 + 12) +#define CPASS_MTX_21 (34 * 16 + 4) +#define CPASS_MTX_22 (34 * 16 + 8) +#define D_EXT_GYRO_BIAS_X (61 * 16) +#define D_EXT_GYRO_BIAS_Y (61 * 16 + 4) +#define D_EXT_GYRO_BIAS_Z (61 * 16 + 8) +#define D_ACT0 (40 * 16) +#define D_ACSX (40 * 16 + 4) +#define D_ACSY (40 * 16 + 8) +#define D_ACSZ (40 * 16 + 12) + +#define FLICK_MSG (45 * 16 + 4) +#define FLICK_COUNTER (45 * 16 + 8) +#define FLICK_LOWER (45 * 16 + 12) +#define FLICK_UPPER (46 * 16 + 12) + +#define D_SMD_ENABLE (18 * 16) +#define D_SMD_MOT_THLD (20 * 16) +#define D_SMD_DELAY_THLD (21 * 16 + 4) +#define D_SMD_DELAY2_THLD (21 * 16 + 12) +#define D_SMD_EXE_STATE (22 * 16) +#define D_SMD_DELAY_CNTR (21 * 16) + +#define D_AUTH_OUT (992) +#define D_AUTH_IN (996) +#define D_AUTH_A (1000) +#define D_AUTH_B (1004) + +#define D_PEDSTD_BP_B (768 + 0x1C) +#define D_PEDSTD_BP_A4 (768 + 0x40) +#define D_PEDSTD_BP_A3 (768 + 0x44) +#define D_PEDSTD_BP_A2 (768 + 0x48) +#define D_PEDSTD_BP_A1 (768 + 0x4C) +#define D_PEDSTD_SB (768 + 0x28) +#define D_PEDSTD_SB_TIME (768 + 0x2C) +#define D_PEDSTD_PEAKTHRSH (768 + 0x98) +#define D_PEDSTD_TIML (768 + 0x2A) +#define D_PEDSTD_TIMH (768 + 0x2E) +#define D_PEDSTD_PEAK (768 + 0X94) +#define D_PEDSTD_STEPCTR (768 + 0x60) +#define D_PEDSTD_TIMECTR (964) +#define D_PEDSTD_DECI (768 + 0xA0) + +#define D_HOST_NO_MOT (976) +#define D_ACCEL_BIAS (660) + +#define D_BM_BATCH_CNTR (27 * 16 + 4) +#define D_BM_BATCH_THLD (27 * 16 + 8) +#define D_BM_ENABLE (28 * 16 + 6) +#define D_BM_NUMWORD_TOFILL (28 * 16 + 4) + +#define D_SO_DATA (4 * 16 + 2) +#define D_P_HW_ID (22 * 16 + 10) +#define D_P_INIT (22 * 16 + 2) + +#define D_DMP_RUN_CNTR (24*16) + +#define D_ODR_S0 (45*16+8) +#define D_ODR_S1 (45*16+12) +#define D_ODR_S2 (45*16+10) +#define D_ODR_S3 (45*16+14) +#define D_ODR_S4 (46*16+8) +#define D_ODR_S5 (46*16+12) +#define D_ODR_S6 (46*16+10) +#define D_ODR_S7 (46*16+14) + +#define D_ODR_CNTR_S0 (45*16) +#define D_ODR_CNTR_S1 (45*16+4) +#define D_ODR_CNTR_S2 (45*16+2) +#define D_ODR_CNTR_S3 (45*16+6) +#define D_ODR_CNTR_S4 (46*16) +#define D_ODR_CNTR_S5 (46*16+4) +#define D_ODR_CNTR_S6 (46*16+2) +#define D_ODR_CNTR_S7 (46*16+6) + +#define D_FS_LPQ0 (59*16) +#define D_FS_LPQ1 (59*16 + 4) +#define D_FS_LPQ2 (59*16 + 8) +#define D_FS_LPQ3 (59*16 + 12) + +#define D_FS_Q0 (12*16) +#define D_FS_Q1 (12*16 + 4) +#define D_FS_Q2 (12*16 + 8) +#define D_FS_Q3 (12*16 + 12) + +#define D_CPASS_VALID (27*16+2) +#define D_9A_ACCURACY (27*16) + +static const struct tKeyLabel dmpTConfig[] = { + {KEY_CFG_OUT_ACCL, CFG_OUT_ACCL}, + {KEY_CFG_OUT_GYRO, CFG_OUT_GYRO}, + {KEY_CFG_OUT_3QUAT, CFG_OUT_3QUAT}, + {KEY_CFG_OUT_QUAT, CFG_OUT_QUAT}, + {KEY_CFG_OUT_PQUAT, CFG_OUT_PQUAT}, + {KEY_CFG_PED_ENABLE, CFG_PED_ENABLE}, + {KEY_CFG_FIFO_INT, CFG_FIFO_INT}, + {KEY_CFG_AUTH, CFG_AUTH}, + {KEY_FCFG_1, FCFG_1}, + {KEY_FCFG_3, FCFG_3}, + {KEY_FCFG_2, FCFG_2}, + {KEY_FCFG_7, FCFG_7}, + {KEY_CFG_7, CFG_7}, + {KEY_CFG_MOTION_BIAS, CFG_MOTION_BIAS}, + {KEY_CFG_PEDSTEP_DET, CFG_PEDSTEP_DET}, + {KEY_D_0_22, D_0_22}, + {KEY_D_0_96, D_0_96}, + {KEY_D_0_104, D_0_104}, + {KEY_D_0_108, D_0_108}, + {KEY_D_1_36, D_1_36}, + {KEY_D_1_40, D_1_40}, + {KEY_D_1_44, D_1_44}, + {KEY_D_1_72, D_1_72}, + {KEY_D_1_74, D_1_74}, + {KEY_D_1_79, D_1_79}, + {KEY_D_1_88, D_1_88}, + {KEY_D_1_90, D_1_90}, + {KEY_D_1_92, D_1_92}, + {KEY_D_1_160, D_1_160}, + {KEY_D_1_176, D_1_176}, + {KEY_D_1_218, D_1_218}, + {KEY_D_1_232, D_1_232}, + {KEY_D_1_250, D_1_250}, + {KEY_DMP_TAP_GATE, DMP_TAP_GATE}, + {KEY_DMP_TAP_THR_Z, DMP_TAP_THZ}, + {KEY_DMP_TAP_PREV_JERK_Z, DMP_TAP_PREV_JERK_Z}, + {KEY_DMP_TAP_MIN_TAPS, DMP_TAP_MIN_TAPS}, + {KEY_DMP_TAPW_MIN, DMP_TAPW_MIN}, + {KEY_DMP_TAP_NEXT_TAP_THRES, DMP_TAP_NEXT_TAP_THRES}, + {KEY_DMP_TAP_SHAKE_REJECT, DMP_TAP_SHAKE_REJECT}, + {KEY_DMP_TAP_SHAKE_COUNT_MAX, DMP_TAP_SHAKE_COUNT_MAX}, + {KEY_DMP_TAP_SHAKE_TIMEOUT_MAX, DMP_TAP_SHAKE_TIMEOUT_MAX}, + {KEY_DMP_TAP_DIRECTION, DMP_TAP_DIRECTION}, + {KEY_DMP_TAP_COUNT, DMP_TAP_COUNT}, + {KEY_DMP_SH_TH_Y, DMP_SH_TH_Y}, + {KEY_DMP_SH_TH_X, DMP_SH_TH_X}, + {KEY_DMP_SH_TH_Z, DMP_SH_TH_Z}, + {KEY_DMP_ORIENT, DMP_ORIENT}, + {KEY_D_AUTH_OUT, D_AUTH_OUT}, + {KEY_D_AUTH_IN, D_AUTH_IN}, + {KEY_D_AUTH_A, D_AUTH_A}, + {KEY_D_AUTH_B, D_AUTH_B}, + {KEY_CPASS_BIAS_X, CPASS_BIAS_X}, + {KEY_CPASS_BIAS_Y, CPASS_BIAS_Y}, + {KEY_CPASS_BIAS_Z, CPASS_BIAS_Z}, + {KEY_CPASS_MTX_00, CPASS_MTX_00}, + {KEY_CPASS_MTX_01, CPASS_MTX_01}, + {KEY_CPASS_MTX_02, CPASS_MTX_02}, + {KEY_CPASS_MTX_10, CPASS_MTX_10}, + {KEY_CPASS_MTX_11, CPASS_MTX_11}, + {KEY_CPASS_MTX_12, CPASS_MTX_12}, + {KEY_CPASS_MTX_20, CPASS_MTX_20}, + {KEY_CPASS_MTX_21, CPASS_MTX_21}, + {KEY_CPASS_MTX_22, CPASS_MTX_22}, + {KEY_D_ACT0, D_ACT0}, + {KEY_D_ACSX, D_ACSX}, + {KEY_D_ACSY, D_ACSY}, + {KEY_D_ACSZ, D_ACSZ}, + {KEY_D_PEDSTD_BP_B, D_PEDSTD_BP_B}, + {KEY_D_PEDSTD_BP_A4, D_PEDSTD_BP_A4}, + {KEY_D_PEDSTD_BP_A3, D_PEDSTD_BP_A3}, + {KEY_D_PEDSTD_BP_A2, D_PEDSTD_BP_A2}, + {KEY_D_PEDSTD_BP_A1, D_PEDSTD_BP_A1}, + {KEY_D_PEDSTD_SB, D_PEDSTD_SB}, + {KEY_D_PEDSTD_SB_TIME, D_PEDSTD_SB_TIME}, + {KEY_D_PEDSTD_PEAKTHRSH, D_PEDSTD_PEAKTHRSH}, + {KEY_D_PEDSTD_TIML, D_PEDSTD_TIML}, + {KEY_D_PEDSTD_TIMH, D_PEDSTD_TIMH}, + {KEY_D_PEDSTD_PEAK, D_PEDSTD_PEAK}, + {KEY_D_PEDSTD_STEPCTR, D_PEDSTD_STEPCTR}, + {KEY_D_PEDSTD_TIMECTR, D_PEDSTD_TIMECTR}, + {KEY_D_PEDSTD_DECI, D_PEDSTD_DECI}, + {KEY_D_HOST_NO_MOT, D_HOST_NO_MOT}, + {KEY_D_ACCEL_BIAS, D_ACCEL_BIAS}, + {KEY_CFG_EXT_GYRO_BIAS_X, D_EXT_GYRO_BIAS_X}, + {KEY_CFG_EXT_GYRO_BIAS_Y, D_EXT_GYRO_BIAS_Y}, + {KEY_CFG_EXT_GYRO_BIAS_Z, D_EXT_GYRO_BIAS_Z}, + {KEY_CFG_PED_INT, CFG_PED_INT}, + {KEY_SMD_ENABLE, D_SMD_ENABLE}, + {KEY_SMD_ACCEL_THLD, D_SMD_MOT_THLD}, + {KEY_SMD_DELAY_THLD, D_SMD_DELAY_THLD}, + {KEY_SMD_DELAY2_THLD, D_SMD_DELAY2_THLD}, + {KEY_SMD_ENABLE_TESTPT1, SMD_TP1}, + {KEY_SMD_ENABLE_TESTPT2, SMD_TP2}, + {KEY_SMD_EXE_STATE, D_SMD_EXE_STATE}, + {KEY_SMD_DELAY_CNTR, D_SMD_DELAY_CNTR}, + {KEY_BM_ENABLE, D_BM_ENABLE}, + {KEY_BM_BATCH_CNTR, D_BM_BATCH_CNTR}, + {KEY_BM_BATCH_THLD, D_BM_BATCH_THLD}, + {KEY_BM_NUMWORD_TOFILL, D_BM_NUMWORD_TOFILL}, + {KEY_SO_DATA, D_SO_DATA}, + {KEY_P_HW_ID, D_P_HW_ID}, + {KEY_P_INIT, D_P_INIT}, + {KEY_CFG_OUT_CPASS, CFG_OUT_CPASS}, + {KEY_CFG_OUT_PRESS, CFG_OUT_PRESS}, + {KEY_CFG_OUT_STEPDET, CFG_OUT_STEPDET}, + {KEY_CFG_3QUAT_ODR, D_ODR_S1}, + {KEY_CFG_QUAT_ODR, D_ODR_S2}, + {KEY_CFG_PQUAT_ODR, D_ODR_S3}, + {KEY_CFG_ACCL_ODR, D_ODR_S4}, + {KEY_CFG_GYRO_ODR, D_ODR_S5}, + {KEY_CFG_CPASS_ODR, D_ODR_S6}, + {KEY_CFG_PRESS_ODR, D_ODR_S7}, + {KEY_ODR_CNTR_3QUAT, D_ODR_CNTR_S1}, + {KEY_ODR_CNTR_QUAT, D_ODR_CNTR_S2}, + {KEY_ODR_CNTR_PQUAT, D_ODR_CNTR_S3}, + {KEY_ODR_CNTR_ACCL, D_ODR_CNTR_S4}, + {KEY_ODR_CNTR_GYRO, D_ODR_CNTR_S5}, + {KEY_ODR_CNTR_CPASS, D_ODR_CNTR_S6}, + {KEY_ODR_CNTR_PRESS, D_ODR_CNTR_S7}, + {KEY_DMP_RUN_CNTR, D_DMP_RUN_CNTR}, + {KEY_DMP_LPQ0, D_FS_LPQ0}, + {KEY_DMP_LPQ1, D_FS_LPQ1}, + {KEY_DMP_LPQ2, D_FS_LPQ2}, + {KEY_DMP_LPQ3, D_FS_LPQ3}, + {KEY_DMP_Q0, D_FS_Q0}, + {KEY_DMP_Q1, D_FS_Q1}, + {KEY_DMP_Q2, D_FS_Q2}, + {KEY_DMP_Q3, D_FS_Q3}, + {KEY_CPASS_VALID, D_CPASS_VALID}, + {KEY_9AXIS_ACCURACY, D_9A_ACCURACY}, + {KEY_TEST_01, OUT_ACCL_DAT}, + {KEY_TEST_02, OUT_GYRO_DAT}, + {KEY_TEST_03, OUT_CPASS_DAT}, + {KEY_TEST_04, OUT_PRESS_DAT}, + {KEY_TEST_05, OUT_3QUAT_DAT}, + {KEY_TEST_06, OUT_QUAT_DAT}, + {KEY_TEST_07, OUT_PQUAT_DAT} +}; +#define NUM_LOCAL_KEYS (sizeof(dmpTConfig)/sizeof(dmpTConfig[0])) + +static struct tKeyLabel keys[NUM_KEYS]; + +unsigned short inv_dmp_get_address(unsigned short key) +{ + static int isSorted; + + if (!isSorted) { + int kk; + for (kk = 0; kk < NUM_KEYS; ++kk) { + keys[kk].addr = 0xffff; + keys[kk].key = kk; + } + for (kk = 0; kk < NUM_LOCAL_KEYS; ++kk) + keys[dmpTConfig[kk].key].addr = dmpTConfig[kk].addr; + isSorted = 1; + } + if (key >= NUM_KEYS) { + pr_err("ERROR!! key not exist=%d!\n", key); + return 0xffff; + } + if (0xffff == keys[key].addr) + pr_err("ERROR!!key not local=%d!\n", key); + return keys[key].addr; +} diff --git a/drivers/iio/imu/inv_mpu9250/dmpKey.h b/drivers/iio/imu/inv_mpu9250/dmpKey.h new file mode 100644 index 0000000000000..3ca44adc91d61 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/dmpKey.h @@ -0,0 +1,616 @@ +/* + * 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. + */ +#ifndef DMPKEY_H__ +#define DMPKEY_H__ + +#define KEY_CFG_25 (0) +#define KEY_CFG_24 (KEY_CFG_25 + 1) +#define KEY_CFG_26 (KEY_CFG_24 + 1) +#define KEY_CFG_27 (KEY_CFG_26 + 1) +#define KEY_CFG_21 (KEY_CFG_27 + 1) +#define KEY_CFG_20 (KEY_CFG_21 + 1) +#define KEY_CFG_TAP4 (KEY_CFG_20 + 1) +#define KEY_CFG_TAP5 (KEY_CFG_TAP4 + 1) +#define KEY_CFG_TAP6 (KEY_CFG_TAP5 + 1) +#define KEY_CFG_TAP7 (KEY_CFG_TAP6 + 1) +#define KEY_CFG_TAP0 (KEY_CFG_TAP7 + 1) +#define KEY_CFG_TAP1 (KEY_CFG_TAP0 + 1) +#define KEY_CFG_TAP2 (KEY_CFG_TAP1 + 1) +#define KEY_CFG_TAP3 (KEY_CFG_TAP2 + 1) +#define KEY_CFG_TAP_QUANTIZE (KEY_CFG_TAP3 + 1) +#define KEY_CFG_TAP_JERK (KEY_CFG_TAP_QUANTIZE + 1) +#define KEY_CFG_DR_INT (KEY_CFG_TAP_JERK + 1) +#define KEY_CFG_AUTH (KEY_CFG_DR_INT + 1) +#define KEY_CFG_TAP_SAVE_ACCB (KEY_CFG_AUTH + 1) +#define KEY_CFG_TAP_CLEAR_STICKY (KEY_CFG_TAP_SAVE_ACCB + 1) +#define KEY_CFG_FIFO_ON_EVENT (KEY_CFG_TAP_CLEAR_STICKY + 1) +#define KEY_FCFG_ACCEL_INPUT (KEY_CFG_FIFO_ON_EVENT + 1) +#define KEY_FCFG_ACCEL_INIT (KEY_FCFG_ACCEL_INPUT + 1) +#define KEY_CFG_23 (KEY_FCFG_ACCEL_INIT + 1) +#define KEY_FCFG_1 (KEY_CFG_23 + 1) +#define KEY_FCFG_3 (KEY_FCFG_1 + 1) +#define KEY_FCFG_2 (KEY_FCFG_3 + 1) +#define KEY_CFG_3D (KEY_FCFG_2 + 1) +#define KEY_CFG_3B (KEY_CFG_3D + 1) +#define KEY_CFG_3C (KEY_CFG_3B + 1) +#define KEY_FCFG_5 (KEY_CFG_3C + 1) +#define KEY_FCFG_4 (KEY_FCFG_5 + 1) +#define KEY_FCFG_7 (KEY_FCFG_4 + 1) +#define KEY_FCFG_FSCALE (KEY_FCFG_7 + 1) +#define KEY_FCFG_AZ (KEY_FCFG_FSCALE + 1) +#define KEY_FCFG_6 (KEY_FCFG_AZ + 1) +#define KEY_FCFG_LSB4 (KEY_FCFG_6 + 1) +#define KEY_CFG_12 (KEY_FCFG_LSB4 + 1) +#define KEY_CFG_14 (KEY_CFG_12 + 1) +#define KEY_CFG_15 (KEY_CFG_14 + 1) +#define KEY_CFG_16 (KEY_CFG_15 + 1) +#define KEY_CFG_18 (KEY_CFG_16 + 1) +#define KEY_CFG_6 (KEY_CFG_18 + 1) +#define KEY_CFG_7 (KEY_CFG_6 + 1) +#define KEY_CFG_4 (KEY_CFG_7 + 1) +#define KEY_CFG_5 (KEY_CFG_4 + 1) +#define KEY_CFG_2 (KEY_CFG_5 + 1) +#define KEY_CFG_3 (KEY_CFG_2 + 1) +#define KEY_CFG_1 (KEY_CFG_3 + 1) +#define KEY_CFG_EXTERNAL (KEY_CFG_1 + 1) +#define KEY_CFG_8 (KEY_CFG_EXTERNAL + 1) +#define KEY_CFG_9 (KEY_CFG_8 + 1) +#define KEY_CFG_ORIENT_3 (KEY_CFG_9 + 1) +#define KEY_CFG_ORIENT_2 (KEY_CFG_ORIENT_3 + 1) +#define KEY_CFG_ORIENT_1 (KEY_CFG_ORIENT_2 + 1) +#define KEY_CFG_GYRO_SOURCE (KEY_CFG_ORIENT_1 + 1) +#define KEY_CFG_ORIENT_IRQ_1 (KEY_CFG_GYRO_SOURCE + 1) +#define KEY_CFG_ORIENT_IRQ_2 (KEY_CFG_ORIENT_IRQ_1 + 1) +#define KEY_CFG_ORIENT_IRQ_3 (KEY_CFG_ORIENT_IRQ_2 + 1) +#define KEY_FCFG_MAG_VAL (KEY_CFG_ORIENT_IRQ_3 + 1) +#define KEY_FCFG_MAG_MOV (KEY_FCFG_MAG_VAL + 1) +#define KEY_CFG_LP_QUAT (KEY_FCFG_MAG_MOV + 1) +#define KEY_CFG_GYRO_RAW_DATA (KEY_CFG_LP_QUAT + 1) +#define KEY_CFG_EXT_GYRO_BIAS (KEY_CFG_GYRO_RAW_DATA + 1) +#define KEY_CFG_EXT_GYRO_BIAS_X (KEY_CFG_EXT_GYRO_BIAS + 1) +#define KEY_CFG_EXT_GYRO_BIAS_Y (KEY_CFG_EXT_GYRO_BIAS_X + 1) +#define KEY_CFG_EXT_GYRO_BIAS_Z (KEY_CFG_EXT_GYRO_BIAS_Y + 1) +#define KEY_bad_compass (KEY_CFG_EXT_GYRO_BIAS_Z + 1) +#define KEY_COMPASS_CHG_SENSITIVITY (KEY_bad_compass + 1) +#define KEY_CCS_HEADING_THLD (KEY_COMPASS_CHG_SENSITIVITY + 1) +#define KEY_CCS_TIME_THLD (KEY_CCS_HEADING_THLD + 1) +#define KEY_CCS_DOTP_THLD (KEY_CCS_TIME_THLD + 1) +#define KEY_CCS_COMP_CNTR (KEY_CCS_DOTP_THLD + 1) +#define KEY_CFG_NM_DET (KEY_CCS_COMP_CNTR + 1) +#define KEY_SMD_ENABLE (KEY_CFG_NM_DET + 1) +#define KEY_SMD_ACCEL_THLD (KEY_SMD_ENABLE + 1) +#define KEY_SMD_DELAY_THLD (KEY_SMD_ACCEL_THLD + 1) +#define KEY_SMD_DELAY2_THLD (KEY_SMD_DELAY_THLD + 1) +#define KEY_SMD_ENABLE_TESTPT1 (KEY_SMD_DELAY2_THLD + 1) +#define KEY_SMD_ENABLE_TESTPT2 (KEY_SMD_ENABLE_TESTPT1 + 1) +#define KEY_SMD_EXE_STATE (KEY_SMD_ENABLE_TESTPT2 + 1) +#define KEY_SMD_DELAY_CNTR (KEY_SMD_EXE_STATE + 1) + +#define KEY_BREAK (81) +#if KEY_SMD_DELAY_CNTR != KEY_BREAK +#error +#endif + +/* MPU6050 keys */ +#define KEY_CFG_ACCEL_FILTER (KEY_BREAK + 1) +#define KEY_CFG_MOTION_BIAS (KEY_CFG_ACCEL_FILTER + 1) +#define KEY_TEMPLABEL (KEY_CFG_MOTION_BIAS + 1) + +#define KEY_D_0_22 (KEY_TEMPLABEL + 1) +#define KEY_D_0_24 (KEY_D_0_22 + 1) +#define KEY_D_0_36 (KEY_D_0_24 + 1) +#define KEY_D_0_52 (KEY_D_0_36 + 1) +#define KEY_D_0_96 (KEY_D_0_52 + 1) +#define KEY_D_0_104 (KEY_D_0_96 + 1) +#define KEY_D_0_108 (KEY_D_0_104 + 1) +#define KEY_D_0_163 (KEY_D_0_108 + 1) +#define KEY_D_0_188 (KEY_D_0_163 + 1) +#define KEY_D_0_192 (KEY_D_0_188 + 1) +#define KEY_D_0_224 (KEY_D_0_192 + 1) +#define KEY_D_0_228 (KEY_D_0_224 + 1) +#define KEY_D_0_232 (KEY_D_0_228 + 1) +#define KEY_D_0_236 (KEY_D_0_232 + 1) + +#define KEY_DMP_PREVPTAT (KEY_D_0_236 + 1) +#define KEY_D_1_2 (KEY_DMP_PREVPTAT + 1) +#define KEY_D_1_4 (KEY_D_1_2 + 1) +#define KEY_D_1_8 (KEY_D_1_4 + 1) +#define KEY_D_1_10 (KEY_D_1_8 + 1) +#define KEY_D_1_24 (KEY_D_1_10 + 1) +#define KEY_D_1_28 (KEY_D_1_24 + 1) +#define KEY_D_1_36 (KEY_D_1_28 + 1) +#define KEY_D_1_40 (KEY_D_1_36 + 1) +#define KEY_D_1_44 (KEY_D_1_40 + 1) +#define KEY_D_1_72 (KEY_D_1_44 + 1) +#define KEY_D_1_74 (KEY_D_1_72 + 1) +#define KEY_D_1_79 (KEY_D_1_74 + 1) +#define KEY_D_1_88 (KEY_D_1_79 + 1) +#define KEY_D_1_90 (KEY_D_1_88 + 1) +#define KEY_D_1_92 (KEY_D_1_90 + 1) +#define KEY_D_1_96 (KEY_D_1_92 + 1) +#define KEY_D_1_98 (KEY_D_1_96 + 1) +#define KEY_D_1_100 (KEY_D_1_98 + 1) +#define KEY_D_1_106 (KEY_D_1_100 + 1) +#define KEY_D_1_108 (KEY_D_1_106 + 1) +#define KEY_D_1_112 (KEY_D_1_108 + 1) +#define KEY_D_1_128 (KEY_D_1_112 + 1) +#define KEY_D_1_152 (KEY_D_1_128 + 1) +#define KEY_D_1_160 (KEY_D_1_152 + 1) +#define KEY_D_1_168 (KEY_D_1_160 + 1) +#define KEY_D_1_175 (KEY_D_1_168 + 1) +#define KEY_D_1_176 (KEY_D_1_175 + 1) +#define KEY_D_1_178 (KEY_D_1_176 + 1) +#define KEY_D_1_179 (KEY_D_1_178 + 1) +#define KEY_D_1_218 (KEY_D_1_179 + 1) +#define KEY_D_1_232 (KEY_D_1_218 + 1) +#define KEY_D_1_236 (KEY_D_1_232 + 1) +#define KEY_D_1_240 (KEY_D_1_236 + 1) +#define KEY_D_1_244 (KEY_D_1_240 + 1) +#define KEY_D_1_250 (KEY_D_1_244 + 1) +#define KEY_D_1_252 (KEY_D_1_250 + 1) +#define KEY_D_2_12 (KEY_D_1_252 + 1) +#define KEY_D_2_96 (KEY_D_2_12 + 1) +#define KEY_D_2_108 (KEY_D_2_96 + 1) +#define KEY_D_2_208 (KEY_D_2_108 + 1) +#define KEY_FLICK_MSG (KEY_D_2_208 + 1) +#define KEY_FLICK_COUNTER (KEY_FLICK_MSG + 1) +#define KEY_FLICK_LOWER (KEY_FLICK_COUNTER + 1) +#define KEY_CFG_FLICK_IN (KEY_FLICK_LOWER + 1) +#define KEY_FLICK_UPPER (KEY_CFG_FLICK_IN + 1) +#define KEY_CGNOTICE_INTR (KEY_FLICK_UPPER + 1) +#define KEY_D_2_224 (KEY_CGNOTICE_INTR + 1) +#define KEY_D_2_244 (KEY_D_2_224 + 1) +#define KEY_D_2_248 (KEY_D_2_244 + 1) +#define KEY_D_2_252 (KEY_D_2_248 + 1) + +#define KEY_D_GYRO_BIAS_X (KEY_D_2_252 + 1) +#define KEY_D_GYRO_BIAS_Y (KEY_D_GYRO_BIAS_X + 1) +#define KEY_D_GYRO_BIAS_Z (KEY_D_GYRO_BIAS_Y + 1) +#define KEY_D_ACC_BIAS_X (KEY_D_GYRO_BIAS_Z + 1) +#define KEY_D_ACC_BIAS_Y (KEY_D_ACC_BIAS_X + 1) +#define KEY_D_ACC_BIAS_Z (KEY_D_ACC_BIAS_Y + 1) +#define KEY_D_GYRO_ENABLE (KEY_D_ACC_BIAS_Z + 1) +#define KEY_D_ACCEL_ENABLE (KEY_D_GYRO_ENABLE + 1) +#define KEY_D_QUAT_ENABLE (KEY_D_ACCEL_ENABLE + 1) +#define KEY_D_OUTPUT_ENABLE (KEY_D_QUAT_ENABLE + 1) +#define KEY_D_ACCEL_CNTR (KEY_D_OUTPUT_ENABLE + 1) +#define KEY_D_GYRO_CNTR (KEY_D_ACCEL_CNTR + 1) +#define KEY_D_QUAT0_CNTR (KEY_D_GYRO_CNTR + 1) +#define KEY_D_QUAT1_CNTR (KEY_D_QUAT0_CNTR + 1) +#define KEY_D_QUAT2_CNTR (KEY_D_QUAT1_CNTR + 1) +#define KEY_D_CR_TIME_G (KEY_D_QUAT2_CNTR + 1) +#define KEY_D_CR_TIME_A (KEY_D_CR_TIME_G + 1) +#define KEY_D_CR_TIME_Q (KEY_D_CR_TIME_A + 1) +#define KEY_D_CS_TAX (KEY_D_CR_TIME_Q + 1) +#define KEY_D_CS_TAY (KEY_D_CS_TAX + 1) +#define KEY_D_CS_TAZ (KEY_D_CS_TAY + 1) +#define KEY_D_CS_TGX (KEY_D_CS_TAZ + 1) +#define KEY_D_CS_TGY (KEY_D_CS_TGX + 1) +#define KEY_D_CS_TGZ (KEY_D_CS_TGY + 1) +#define KEY_D_CS_TQ0 (KEY_D_CS_TGZ + 1) +#define KEY_D_CS_TQ1 (KEY_D_CS_TQ0 + 1) +#define KEY_D_CS_TQ2 (KEY_D_CS_TQ1 + 1) +#define KEY_D_CS_TQ3 (KEY_D_CS_TQ2 + 1) + +/* Compass keys */ +#define KEY_CPASS_GAIN (KEY_D_CS_TQ3 + 1) +#define KEY_CPASS_BIAS_X (KEY_CPASS_GAIN + 1) +#define KEY_CPASS_BIAS_Y (KEY_CPASS_BIAS_X + 1) +#define KEY_CPASS_BIAS_Z (KEY_CPASS_BIAS_Y + 1) +#define KEY_CPASS_MTX_00 (KEY_CPASS_BIAS_Z + 1) +#define KEY_CPASS_MTX_01 (KEY_CPASS_MTX_00 + 1) +#define KEY_CPASS_MTX_02 (KEY_CPASS_MTX_01 + 1) +#define KEY_CPASS_MTX_10 (KEY_CPASS_MTX_02 + 1) +#define KEY_CPASS_MTX_11 (KEY_CPASS_MTX_10 + 1) +#define KEY_CPASS_MTX_12 (KEY_CPASS_MTX_11 + 1) +#define KEY_CPASS_MTX_20 (KEY_CPASS_MTX_12 + 1) +#define KEY_CPASS_MTX_21 (KEY_CPASS_MTX_20 + 1) +#define KEY_CPASS_MTX_22 (KEY_CPASS_MTX_21 + 1) + +/* Tap Keys */ +#define KEY_DMP_TAP_GATE (KEY_CPASS_MTX_22 + 1) +#define KEY_DMP_TAPW_MIN (KEY_DMP_TAP_GATE + 1) +#define KEY_DMP_TAP_THR_Z (KEY_DMP_TAPW_MIN + 1) +#define KEY_DMP_TAP_PREV_JERK_Z (KEY_DMP_TAP_THR_Z + 1) +#define KEY_DMP_TAP_MIN_TAPS (KEY_DMP_TAP_PREV_JERK_Z + 1) +#define KEY_DMP_TAP_NEXT_TAP_THRES (KEY_DMP_TAP_MIN_TAPS + 1) +#define KEY_DMP_TAP_SHAKE_REJECT (KEY_DMP_TAP_NEXT_TAP_THRES + 1) +#define KEY_DMP_TAP_SHAKE_COUNT_MAX (KEY_DMP_TAP_SHAKE_REJECT + 1) +#define KEY_DMP_TAP_SHAKE_TIMEOUT_MAX (KEY_DMP_TAP_SHAKE_COUNT_MAX + 1) +#define KEY_DMP_TAP_DIRECTION (KEY_DMP_TAP_SHAKE_TIMEOUT_MAX + 1) +#define KEY_DMP_TAP_COUNT (KEY_DMP_TAP_DIRECTION + 1) + +/* Gesture Keys */ +#define KEY_DMP_SH_TH_Y (KEY_DMP_TAP_COUNT + 1) +#define KEY_DMP_SH_TH_X (KEY_DMP_SH_TH_Y + 1) +#define KEY_DMP_SH_TH_Z (KEY_DMP_SH_TH_X + 1) +#define KEY_DMP_ORIENT (KEY_DMP_SH_TH_Z + 1) +#define KEY_D_ACT0 (KEY_DMP_ORIENT + 1) +#define KEY_D_ACSX (KEY_D_ACT0 + 1) +#define KEY_D_ACSY (KEY_D_ACSX + 1) +#define KEY_D_ACSZ (KEY_D_ACSY + 1) + +#define KEY_X_GRT_Y_TMP (KEY_D_ACSZ + 1) +#define KEY_SKIP_X_GRT_Y_TMP (KEY_X_GRT_Y_TMP + 1) +#define KEY_SKIP_END_COMPARE (KEY_SKIP_X_GRT_Y_TMP + 1) +#define KEY_END_COMPARE_Y_X_TMP2 (KEY_SKIP_END_COMPARE + 1) +#define KEY_CFG_DISPLAY_ORIENT_INT (KEY_END_COMPARE_Y_X_TMP2 + 1) +#define KEY_NO_ORIENT_INTERRUPT (KEY_CFG_DISPLAY_ORIENT_INT + 1) +#define KEY_END_COMPARE_Y_X_TMP (KEY_NO_ORIENT_INTERRUPT + 1) +#define KEY_END_ORIENT_1 (KEY_END_COMPARE_Y_X_TMP + 1) +#define KEY_END_COMPARE_Y_X (KEY_END_ORIENT_1 + 1) +#define KEY_END_ORIENT (KEY_END_COMPARE_Y_X + 1) +#define KEY_X_GRT_Y (KEY_END_ORIENT + 1) +#define KEY_NOT_TIME_MINUS_1 (KEY_X_GRT_Y + 1) +#define KEY_END_COMPARE_Y_X_TMP3 (KEY_NOT_TIME_MINUS_1 + 1) +#define KEY_X_GRT_Y_TMP2 (KEY_END_COMPARE_Y_X_TMP3 + 1) + +/*Shake Keys */ +#define KEY_D_0_64 (KEY_X_GRT_Y_TMP2 + 1) +#define KEY_D_2_4 (KEY_D_0_64 + 1) +#define KEY_D_2_8 (KEY_D_2_4 + 1) +#define KEY_D_2_48 (KEY_D_2_8 + 1) +#define KEY_D_2_92 (KEY_D_2_48 + 1) +#define KEY_D_2_94 (KEY_D_2_92 + 1) +#define KEY_D_2_160 (KEY_D_2_94 + 1) +#define KEY_D_3_180 (KEY_D_2_160 + 1) +#define KEY_D_3_184 (KEY_D_3_180 + 1) +#define KEY_D_3_188 (KEY_D_3_184 + 1) +#define KEY_D_3_208 (KEY_D_3_188 + 1) +#define KEY_D_3_240 (KEY_D_3_208 + 1) +#define KEY_RETRACTION_1 (KEY_D_3_240 + 1) +#define KEY_RETRACTION_2 (KEY_RETRACTION_1 + 1) +#define KEY_RETRACTION_3 (KEY_RETRACTION_2 + 1) +#define KEY_RETRACTION_4 (KEY_RETRACTION_3 + 1) +#define KEY_CFG_SHAKE_INT (KEY_RETRACTION_4 + 1) + +/* Authenticate Keys */ +#define KEY_D_AUTH_OUT (KEY_CFG_SHAKE_INT + 1) +#define KEY_D_AUTH_IN (KEY_D_AUTH_OUT + 1) +#define KEY_D_AUTH_A (KEY_D_AUTH_IN + 1) +#define KEY_D_AUTH_B (KEY_D_AUTH_A + 1) + +/* Pedometer standalone only keys */ +#define KEY_D_PEDSTD_BP_B (KEY_D_AUTH_B + 1) +#define KEY_D_PEDSTD_HP_A (KEY_D_PEDSTD_BP_B + 1) +#define KEY_D_PEDSTD_HP_B (KEY_D_PEDSTD_HP_A + 1) +#define KEY_D_PEDSTD_BP_A4 (KEY_D_PEDSTD_HP_B + 1) +#define KEY_D_PEDSTD_BP_A3 (KEY_D_PEDSTD_BP_A4 + 1) +#define KEY_D_PEDSTD_BP_A2 (KEY_D_PEDSTD_BP_A3 + 1) +#define KEY_D_PEDSTD_BP_A1 (KEY_D_PEDSTD_BP_A2 + 1) +#define KEY_D_PEDSTD_INT_THRSH (KEY_D_PEDSTD_BP_A1 + 1) +#define KEY_D_PEDSTD_CLIP (KEY_D_PEDSTD_INT_THRSH + 1) +#define KEY_D_PEDSTD_SB (KEY_D_PEDSTD_CLIP + 1) +#define KEY_D_PEDSTD_SB_TIME (KEY_D_PEDSTD_SB + 1) +#define KEY_D_PEDSTD_PEAKTHRSH (KEY_D_PEDSTD_SB_TIME + 1) +#define KEY_D_PEDSTD_TIML (KEY_D_PEDSTD_PEAKTHRSH + 1) +#define KEY_D_PEDSTD_TIMH (KEY_D_PEDSTD_TIML + 1) +#define KEY_D_PEDSTD_PEAK (KEY_D_PEDSTD_TIMH + 1) +#define KEY_D_PEDSTD_TIMECTR (KEY_D_PEDSTD_PEAK + 1) +#define KEY_D_PEDSTD_STEPCTR (KEY_D_PEDSTD_TIMECTR + 1) +#define KEY_D_PEDSTD_WALKTIME (KEY_D_PEDSTD_STEPCTR + 1) +#define KEY_D_PEDSTD_DECI (KEY_D_PEDSTD_WALKTIME + 1) +#define KEY_CFG_PED_INT (KEY_D_PEDSTD_DECI + 1) +#define KEY_CFG_PED_ENABLE (KEY_CFG_PED_INT + 1) + +/*Host Based No Motion*/ +#define KEY_D_HOST_NO_MOT (KEY_CFG_PED_ENABLE + 1) + +/*Host Based Accel Bias*/ +#define KEY_D_ACCEL_BIAS (KEY_D_HOST_NO_MOT + 1) + +/*Screen/Display Orientation Keys*/ +#define KEY_D_ORIENT_GAP (KEY_D_ACCEL_BIAS + 1) +#define KEY_D_TILT0_H (KEY_D_ORIENT_GAP + 1) +#define KEY_D_TILT0_L (KEY_D_TILT0_H + 1) +#define KEY_D_TILT1_H (KEY_D_TILT0_L + 1) +#define KEY_D_TILT1_L (KEY_D_TILT1_H + 1) +#define KEY_D_TILT2_H (KEY_D_TILT1_L + 1) +#define KEY_D_TILT2_L (KEY_D_TILT2_H + 1) +#define KEY_D_TILT3_H (KEY_D_TILT2_L + 1) +#define KEY_D_TILT3_L (KEY_D_TILT3_H + 1) + +/* Stream keys */ +#define KEY_STREAM_P_GYRO_Z (KEY_D_TILT3_L + 1) +#define KEY_STREAM_P_GYRO_Y (KEY_STREAM_P_GYRO_Z + 1) +#define KEY_STREAM_P_GYRO_X (KEY_STREAM_P_GYRO_Y + 1) +#define KEY_STREAM_P_TEMP (KEY_STREAM_P_GYRO_X + 1) +#define KEY_STREAM_P_AUX_Y (KEY_STREAM_P_TEMP + 1) +#define KEY_STREAM_P_AUX_X (KEY_STREAM_P_AUX_Y + 1) +#define KEY_STREAM_P_AUX_Z (KEY_STREAM_P_AUX_X + 1) +#define KEY_STREAM_P_ACCEL_Y (KEY_STREAM_P_AUX_Z + 1) +#define KEY_STREAM_P_ACCEL_X (KEY_STREAM_P_ACCEL_Y + 1) +#define KEY_STREAM_P_FOOTER (KEY_STREAM_P_ACCEL_X + 1) +#define KEY_STREAM_P_ACCEL_Z (KEY_STREAM_P_FOOTER + 1) + +/* Batch mode */ +#define KEY_BM_ENABLE (KEY_STREAM_P_ACCEL_Z + 1) +#define KEY_BM_BATCH_THLD (KEY_BM_ENABLE + 1) +#define KEY_BM_BATCH_CNTR (KEY_BM_BATCH_THLD + 1) +#define KEY_BM_NUMWORD_TOFILL (KEY_BM_BATCH_CNTR + 1) + +/* Watermark */ +#define KEY_CFG_WATERMARK_H (KEY_BM_NUMWORD_TOFILL + 1) +#define KEY_CFG_WATERMARK_L (KEY_CFG_WATERMARK_H + 1) + +/* FIFO output control */ +#define KEY_CFG_OUT_ACCL (KEY_CFG_WATERMARK_L + 1) +#define KEY_CFG_OUT_GYRO (KEY_CFG_OUT_ACCL + 1) +#define KEY_CFG_OUT_3QUAT (KEY_CFG_OUT_GYRO + 1) +#define KEY_CFG_OUT_QUAT (KEY_CFG_OUT_3QUAT + 1) +#define KEY_CFG_OUT_PQUAT (KEY_CFG_OUT_QUAT + 1) +#define KEY_CFG_OUT_CPASS (KEY_CFG_OUT_PQUAT + 1) +#define KEY_CFG_OUT_PRESS (KEY_CFG_OUT_CPASS + 1) +#define KEY_CFG_OUT_STEPDET (KEY_CFG_OUT_PRESS + 1) +#define KEY_CFG_FIFO_INT (KEY_CFG_OUT_STEPDET + 1) + +/* Ped Step detection */ +#define KEY_CFG_PEDSTEP_DET (KEY_CFG_FIFO_INT + 1) + +/* Screen Orientation data */ +#define KEY_SO_DATA (KEY_CFG_PEDSTEP_DET + 1) + +/* MPU for DMP Android K */ +#define KEY_P_INIT (KEY_SO_DATA + 1) +#define KEY_P_HW_ID (KEY_P_INIT + 1) + +/* DMP running counter */ +#define KEY_DMP_RUN_CNTR (KEY_P_HW_ID + 1) + +/* Sensor's ODR */ +#define KEY_CFG_3QUAT_ODR (KEY_DMP_RUN_CNTR + 1) +#define KEY_CFG_QUAT_ODR (KEY_CFG_3QUAT_ODR + 1) +#define KEY_CFG_PQUAT_ODR (KEY_CFG_QUAT_ODR + 1) +#define KEY_CFG_ACCL_ODR (KEY_CFG_PQUAT_ODR + 1) +#define KEY_CFG_GYRO_ODR (KEY_CFG_ACCL_ODR + 1) +#define KEY_CFG_CPASS_ODR (KEY_CFG_GYRO_ODR + 1) +#define KEY_CFG_PRESS_ODR (KEY_CFG_CPASS_ODR + 1) + +#define KEY_ODR_CNTR_3QUAT (KEY_CFG_PRESS_ODR + 1) +#define KEY_ODR_CNTR_QUAT (KEY_ODR_CNTR_3QUAT + 1) +#define KEY_ODR_CNTR_PQUAT (KEY_ODR_CNTR_QUAT + 1) +#define KEY_ODR_CNTR_ACCL (KEY_ODR_CNTR_PQUAT + 1) +#define KEY_ODR_CNTR_GYRO (KEY_ODR_CNTR_ACCL + 1) +#define KEY_ODR_CNTR_CPASS (KEY_ODR_CNTR_GYRO + 1) +#define KEY_ODR_CNTR_PRESS (KEY_ODR_CNTR_CPASS + 1) + +/* DMP fusion LP-Quat */ +#define KEY_DMP_LPQ0 (KEY_ODR_CNTR_PRESS + 1) +#define KEY_DMP_LPQ1 (KEY_DMP_LPQ0 + 1) +#define KEY_DMP_LPQ2 (KEY_DMP_LPQ1 + 1) +#define KEY_DMP_LPQ3 (KEY_DMP_LPQ2 + 1) + +/* DMP fusion 6-axis Quat */ +#define KEY_DMP_Q0 (KEY_DMP_LPQ3 + 1) +#define KEY_DMP_Q1 (KEY_DMP_Q0 + 1) +#define KEY_DMP_Q2 (KEY_DMP_Q1 + 1) +#define KEY_DMP_Q3 (KEY_DMP_Q2 + 1) + +/* 9-axis fusion */ +#define KEY_CPASS_VALID (KEY_DMP_Q3 + 1) +#define KEY_9AXIS_ACCURACY (KEY_CPASS_VALID + 1) + +/* Test key */ +#define KEY_TEST_01 (KEY_9AXIS_ACCURACY + 1) +#define KEY_TEST_02 (KEY_TEST_01 + 1) +#define KEY_TEST_03 (KEY_TEST_02 + 1) +#define KEY_TEST_04 (KEY_TEST_03 + 1) +#define KEY_TEST_05 (KEY_TEST_04 + 1) +#define KEY_TEST_06 (KEY_TEST_05 + 1) +#define KEY_TEST_07 (KEY_TEST_06 + 1) +#define KEY_TEST_XX (KEY_TEST_07 + 1) + +#define NUM_KEYS (KEY_TEST_XX + 1) + +struct tKeyLabel { + unsigned short key; + unsigned short addr; +}; + +#define DINA0A 0x0a +#define DINA22 0x22 +#define DINA42 0x42 +#define DINA5A 0x5a + +#define DINA06 0x06 +#define DINA0E 0x0e +#define DINA16 0x16 +#define DINA1E 0x1e +#define DINA26 0x26 +#define DINA2E 0x2e +#define DINA36 0x36 +#define DINA3E 0x3e +#define DINA46 0x46 +#define DINA4E 0x4e +#define DINA56 0x56 +#define DINA5E 0x5e +#define DINA66 0x66 +#define DINA6E 0x6e +#define DINA76 0x76 +#define DINA7E 0x7e + +#define DINA00 0x00 +#define DINA08 0x08 +#define DINA10 0x10 +#define DINA18 0x18 +#define DINA20 0x20 +#define DINA28 0x28 +#define DINA30 0x30 +#define DINA38 0x38 +#define DINA40 0x40 +#define DINA48 0x48 +#define DINA50 0x50 +#define DINA58 0x58 +#define DINA60 0x60 +#define DINA68 0x68 +#define DINA70 0x70 +#define DINA78 0x78 + +#define DINA04 0x04 +#define DINA0C 0x0c +#define DINA14 0x14 +#define DINA1C 0x1C +#define DINA24 0x24 +#define DINA2C 0x2c +#define DINA34 0x34 +#define DINA3C 0x3c +#define DINA44 0x44 +#define DINA4C 0x4c +#define DINA54 0x54 +#define DINA5C 0x5c +#define DINA64 0x64 +#define DINA6C 0x6c +#define DINA74 0x74 +#define DINA7C 0x7c + +#define DINA01 0x01 +#define DINA09 0x09 +#define DINA11 0x11 +#define DINA19 0x19 +#define DINA21 0x21 +#define DINA29 0x29 +#define DINA31 0x31 +#define DINA39 0x39 +#define DINA41 0x41 +#define DINA49 0x49 +#define DINA51 0x51 +#define DINA59 0x59 +#define DINA61 0x61 +#define DINA69 0x69 +#define DINA71 0x71 +#define DINA79 0x79 + +#define DINA25 0x25 +#define DINA2D 0x2d +#define DINA35 0x35 +#define DINA3D 0x3d +#define DINA4D 0x4d +#define DINA55 0x55 +#define DINA5D 0x5D +#define DINA6D 0x6d +#define DINA75 0x75 +#define DINA7D 0x7d + +#define DINADC 0xdc +#define DINAF2 0xf2 +#define DINAAB 0xab +#define DINAAA 0xaa +#define DINAF1 0xf1 +#define DINADF 0xdf +#define DINADA 0xda +#define DINAB1 0xb1 +#define DINAB9 0xb9 +#define DINAF3 0xf3 +#define DINA8B 0x8b +#define DINAA3 0xa3 +#define DINA91 0x91 +#define DINAB6 0xb6 +#define DINAB4 0xb4 + +#define DINC00 0x00 +#define DINC01 0x01 +#define DINC02 0x02 +#define DINC03 0x03 +#define DINC08 0x08 +#define DINC09 0x09 +#define DINC0A 0x0a +#define DINC0B 0x0b +#define DINC10 0x10 +#define DINC11 0x11 +#define DINC12 0x12 +#define DINC13 0x13 +#define DINC18 0x18 +#define DINC19 0x19 +#define DINC1A 0x1a +#define DINC1B 0x1b + +#define DINC20 0x20 +#define DINC21 0x21 +#define DINC22 0x22 +#define DINC23 0x23 +#define DINC28 0x28 +#define DINC29 0x29 +#define DINC2A 0x2a +#define DINC2B 0x2b +#define DINC30 0x30 +#define DINC31 0x31 +#define DINC32 0x32 +#define DINC33 0x33 +#define DINC38 0x38 +#define DINC39 0x39 +#define DINC3A 0x3a +#define DINC3B 0x3b + +#define DINC40 0x40 +#define DINC41 0x41 +#define DINC42 0x42 +#define DINC43 0x43 +#define DINC48 0x48 +#define DINC49 0x49 +#define DINC4A 0x4a +#define DINC4B 0x4b +#define DINC50 0x50 +#define DINC51 0x51 +#define DINC52 0x52 +#define DINC53 0x53 +#define DINC58 0x58 +#define DINC59 0x59 +#define DINC5A 0x5a +#define DINC5B 0x5b + +#define DINC60 0x60 +#define DINC61 0x61 +#define DINC62 0x62 +#define DINC63 0x63 +#define DINC68 0x68 +#define DINC69 0x69 +#define DINC6A 0x6a +#define DINC6B 0x6b +#define DINC70 0x70 +#define DINC71 0x71 +#define DINC72 0x72 +#define DINC73 0x73 +#define DINC78 0x78 +#define DINC79 0x79 +#define DINC7A 0x7a +#define DINC7B 0x7b +#define DIND40 0x40 +#define DINA80 0x80 +#define DINA90 0x90 +#define DINAA0 0xa0 +#define DINAC9 0xc9 +#define DINACB 0xcb +#define DINACD 0xcd +#define DINACF 0xcf +#define DINAC8 0xc8 +#define DINACA 0xca +#define DINACC 0xcc +#define DINACE 0xce +#define DINAD8 0xd8 +#define DINADD 0xdd +#define DINAF8 0xf0 +#define DINAFE 0xfe + +#define DINBF8 0xf8 +#define DINAC0 0xb0 +#define DINAC1 0xb1 +#define DINAC2 0xb4 +#define DINAC3 0xb5 +#define DINAC4 0xb8 +#define DINAC5 0xb9 +#define DINBC0 0xc0 +#define DINBC2 0xc2 +#define DINBC4 0xc4 +#define DINBC6 0xc6 + +#endif diff --git a/drivers/iio/imu/inv_mpu9250/dmpmap.h b/drivers/iio/imu/inv_mpu9250/dmpmap.h new file mode 100644 index 0000000000000..929363722241e --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/dmpmap.h @@ -0,0 +1,263 @@ +/* +* 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. +*/ + +#ifndef DMPMAP_H +#define DMPMAP_H + +#define DMP_PTAT 0 +#define DMP_XGYR 2 +#define DMP_YGYR 4 +#define DMP_ZGYR 6 +#define DMP_XACC 8 +#define DMP_YACC 10 +#define DMP_ZACC 12 +#define DMP_ADC1 14 +#define DMP_ADC2 16 +#define DMP_ADC3 18 +#define DMP_BIASUNC 20 +#define DMP_FIFORT 22 +#define DMP_INVGSFH 24 +#define DMP_INVGSFL 26 +#define DMP_1H 28 +#define DMP_1L 30 +#define DMP_BLPFSTCH 32 +#define DMP_BLPFSTCL 34 +#define DMP_BLPFSXH 36 +#define DMP_BLPFSXL 38 +#define DMP_BLPFSYH 40 +#define DMP_BLPFSYL 42 +#define DMP_BLPFSZH 44 +#define DMP_BLPFSZL 46 +#define DMP_BLPFMTC 48 +#define DMP_SMC 50 +#define DMP_BLPFMXH 52 +#define DMP_BLPFMXL 54 +#define DMP_BLPFMYH 56 +#define DMP_BLPFMYL 58 +#define DMP_BLPFMZH 60 +#define DMP_BLPFMZL 62 +#define DMP_BLPFC 64 +#define DMP_SMCTH 66 +#define DMP_TAP_DIRECTION 68 +#define DMP_TAP_COUNT 70 +#define DMP_TAP_GATE 72 +#define DMP_TAP_MIN_TAPS 74 +#define DMP_BERR2NH 76 +#define DMP_SMCINC 78 +#define DMP_TAP_SHAKE_REJECT 80 +#define DMP_ANGVBXL 82 +#define DMP_TAP_SHAKE_COUNT_MAX 84 +#define DMP_TAP_SHAKE_TIMEOUT_MAX 86 +#define DMP_TAP_THZ 88 +#define DMP_TAPW_MIN 90 +#define DMP_TAP_PREV_JERK_Z 92 +#define DMP_TAP_NEXT_TAP_THRES 94 +#define DMP_ATCH 96 +#define DMP_BIASUNCSF 98 +#define DMP_ACT2H 100 +#define DMP_ACT2L 102 +#define DMP_GSFH 104 +#define DMP_GSFL 106 +#define DMP_GH 108 +#define DMP_GL 110 +#define DMP_0_5H 112 +#define DMP_0_5L 114 +#define DMP_0_0H 116 +#define DMP_0_0L 118 +#define DMP_1_0H 120 +#define DMP_1_0L 122 +#define DMP_1_5H 124 +#define DMP_1_5L 126 +#define DMP_TMP1AH 128 +#define DMP_TMP1AL 130 +#define DMP_TMP2AH 132 +#define DMP_TMP2AL 134 +#define DMP_TMP3AH 136 +#define DMP_TMP3AL 138 +#define DMP_TMP4AH 140 +#define DMP_TMP4AL 142 +#define DMP_XACCW 144 +#define DMP_TMP5 146 +#define DMP_XACCB 148 +#define DMP_TMP8 150 +#define DMP_YACCB 152 +#define DMP_TMP9 154 +#define DMP_ZACCB 156 +#define DMP_TMP10 158 +#define DMP_DZH 160 +#define DMP_DZL 162 +#define DMP_XGCH 164 +#define DMP_XGCL 166 +#define DMP_YGCH 168 +#define DMP_YGCL 170 +#define DMP_ZGCH 172 +#define DMP_ZGCL 174 +#define DMP_YACCW 176 +#define DMP_TMP7 178 +#define DMP_AFB1H 180 +#define DMP_AFB1L 182 +#define DMP_AFB2H 184 +#define DMP_AFB2L 186 +#define DMP_MAGFBH 188 +#define DMP_MAGFBL 190 +#define DMP_QT1H 192 +#define DMP_QT1L 194 +#define DMP_QT2H 196 +#define DMP_QT2L 198 +#define DMP_QT3H 200 +#define DMP_QT3L 202 +#define DMP_QT4H 204 +#define DMP_QT4L 206 +#define DMP_CTRL1H 208 +#define DMP_CTRL1L 210 +#define DMP_CTRL2H 212 +#define DMP_CTRL2L 214 +#define DMP_CTRL3H 216 +#define DMP_CTRL3L 218 +#define DMP_CTRL4H 220 +#define DMP_CTRL4L 222 +#define DMP_CTRLS1 224 +#define DMP_CTRLSF1 226 +#define DMP_CTRLS2 228 +#define DMP_CTRLSF2 230 +#define DMP_CTRLS3 232 +#define DMP_CTRLSFNLL 234 +#define DMP_CTRLS4 236 +#define DMP_CTRLSFNL2 238 +#define DMP_CTRLSFNL 240 +#define DMP_TMP30 242 +#define DMP_CTRLSFJT 244 +#define DMP_TMP31 246 +#define DMP_TMP11 248 +#define DMP_CTRLSF2_2 250 +#define DMP_TMP12 252 +#define DMP_CTRLSF1_2 254 +#define DMP_PREVPTAT 256 +#define DMP_ACCZB 258 +#define DMP_ACCXB 264 +#define DMP_ACCYB 266 +#define DMP_1HB 272 +#define DMP_1LB 274 +#define DMP_0H 276 +#define DMP_0L 278 +#define DMP_ASR22H 280 +#define DMP_ASR22L 282 +#define DMP_ASR6H 284 +#define DMP_ASR6L 286 +#define DMP_TMP13 288 +#define DMP_TMP14 290 +#define DMP_FINTXH 292 +#define DMP_FINTXL 294 +#define DMP_FINTYH 296 +#define DMP_FINTYL 298 +#define DMP_FINTZH 300 +#define DMP_FINTZL 302 +#define DMP_TMP1BH 304 +#define DMP_TMP1BL 306 +#define DMP_TMP2BH 308 +#define DMP_TMP2BL 310 +#define DMP_TMP3BH 312 +#define DMP_TMP3BL 314 +#define DMP_TMP4BH 316 +#define DMP_TMP4BL 318 +#define DMP_STXG 320 +#define DMP_ZCTXG 322 +#define DMP_STYG 324 +#define DMP_ZCTYG 326 +#define DMP_STZG 328 +#define DMP_ZCTZG 330 +#define DMP_CTRLSFJT2 332 +#define DMP_CTRLSFJTCNT 334 +#define DMP_PVXG 336 +#define DMP_TMP15 338 +#define DMP_PVYG 340 +#define DMP_TMP16 342 +#define DMP_PVZG 344 +#define DMP_TMP17 346 +#define DMP_MNMFLAGH 352 +#define DMP_MNMFLAGL 354 +#define DMP_MNMTMH 356 +#define DMP_MNMTML 358 +#define DMP_MNMTMTHRH 360 +#define DMP_MNMTMTHRL 362 +#define DMP_MNMTHRH 364 +#define DMP_MNMTHRL 366 +#define DMP_ACCQD4H 368 +#define DMP_ACCQD4L 370 +#define DMP_ACCQD5H 372 +#define DMP_ACCQD5L 374 +#define DMP_ACCQD6H 376 +#define DMP_ACCQD6L 378 +#define DMP_ACCQD7H 380 +#define DMP_ACCQD7L 382 +#define DMP_ACCQD0H 384 +#define DMP_ACCQD0L 386 +#define DMP_ACCQD1H 388 +#define DMP_ACCQD1L 390 +#define DMP_ACCQD2H 392 +#define DMP_ACCQD2L 394 +#define DMP_ACCQD3H 396 +#define DMP_ACCQD3L 398 +#define DMP_XN2H 400 +#define DMP_XN2L 402 +#define DMP_XN1H 404 +#define DMP_XN1L 406 +#define DMP_YN2H 408 +#define DMP_YN2L 410 +#define DMP_YN1H 412 +#define DMP_YN1L 414 +#define DMP_YH 416 +#define DMP_YL 418 +#define DMP_B0H 420 +#define DMP_B0L 422 +#define DMP_A1H 424 +#define DMP_A1L 426 +#define DMP_A2H 428 +#define DMP_A2L 430 +#define DMP_SEM1 432 +#define DMP_FIFOCNT 434 +#define DMP_SH_TH_X 436 +#define DMP_PACKET 438 +#define DMP_SH_TH_Y 440 +#define DMP_FOOTER 442 +#define DMP_SH_TH_Z 444 +#define DMP_TEMP29 448 +#define DMP_TEMP30 450 +#define DMP_XACCB_PRE 452 +#define DMP_XACCB_PREL 454 +#define DMP_YACCB_PRE 456 +#define DMP_YACCB_PREL 458 +#define DMP_ZACCB_PRE 460 +#define DMP_ZACCB_PREL 462 +#define DMP_TMP22 464 +#define DMP_TAP_TIMER 466 +#define DMP_TAP_THX 468 +#define DMP_TAP_THY 472 +#define DMP_TMP25 480 +#define DMP_TMP26 482 +#define DMP_TMP27 484 +#define DMP_TMP28 486 +#define DMP_ORIENT 488 +#define DMP_THRSH 490 +#define DMP_ENDIANH 492 +#define DMP_ENDIANL 494 +#define DMP_BLPFNMTCH 496 +#define DMP_BLPFNMTCL 498 +#define DMP_BLPFNMXH 500 +#define DMP_BLPFNMXL 502 +#define DMP_BLPFNMYH 504 +#define DMP_BLPFNMYL 506 +#define DMP_BLPFNMZH 508 +#define DMP_BLPFNMZL 510 + +#endif diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu3050_iio.c b/drivers/iio/imu/inv_mpu9250/inv_mpu3050_iio.c new file mode 100644 index 0000000000000..9139d2b5aeeed --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu3050_iio.c @@ -0,0 +1,270 @@ +/* +* 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 +#include +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" +#define MPU3050_NACK_MIN_TIME (2 * 1000) +#define MPU3050_NACK_MAX_TIME (3 * 1000) + +#define MPU3050_ONE_MPU_TIME 20 +#define MPU3050_BOGUS_ADDR 0x7F +int __attribute__((weak)) inv_register_mpu3050_slave(struct inv_mpu_state *st) +{ + return 0; +} + +int set_3050_bypass(struct inv_mpu_state *st, bool enable) +{ + struct inv_reg_map_s *reg; + int result; + u8 b; + + reg = &st->reg; + result = inv_i2c_read(st, reg->user_ctrl, 1, &b); + if (result) + return result; + if (((b & BIT_3050_AUX_IF_EN) == 0) && enable) + return 0; + if ((b & BIT_3050_AUX_IF_EN) && (enable == 0)) + return 0; + b &= ~BIT_3050_AUX_IF_EN; + if (!enable) { + b |= BIT_3050_AUX_IF_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, b); + return result; + } else { + /* Coming out of I2C is tricky due to several erratta. Do not + * modify this algorithm + */ + /* + * 1) wait for the right time and send the command to change + * the aux i2c slave address to an invalid address that will + * get nack'ed + * + * 0x00 is broadcast. 0x7F is unlikely to be used by any aux. + */ + result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR, + MPU3050_BOGUS_ADDR); + if (result) + return result; + /* + * 2) wait enough time for a nack to occur, then go into + * bypass mode: + */ + usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME); + result = inv_i2c_single_write(st, reg->user_ctrl, b); + if (result) + return result; + /* + * 3) wait for up to one MPU cycle then restore the slave + * address + */ + msleep(MPU3050_ONE_MPU_TIME); + + result = inv_i2c_single_write(st, REG_3050_SLAVE_ADDR, + st->plat_data.secondary_i2c_addr); + if (result) + return result; + result = inv_i2c_single_write(st, reg->user_ctrl, b); + if (result) + return result; + usleep_range(MPU3050_NACK_MIN_TIME, MPU3050_NACK_MAX_TIME); + } + return 0; +} + +void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg) +{ + reg->fifo_en = REG_3050_FIFO_EN; + reg->sample_rate_div = REG_3050_SAMPLE_RATE_DIV; + reg->lpf = REG_3050_LPF; + reg->fifo_count_h = REG_3050_FIFO_COUNT_H; + reg->fifo_r_w = REG_3050_FIFO_R_W; + reg->user_ctrl = REG_3050_USER_CTRL; + reg->pwr_mgmt_1 = REG_3050_PWR_MGMT_1; + reg->raw_accel = REG_3050_AUX_XOUT_H; + reg->temperature = REG_3050_TEMPERATURE; + reg->int_enable = REG_3050_INT_ENABLE; + reg->int_status = REG_3050_INT_STATUS; +} + +int inv_switch_3050_gyro_engine(struct inv_mpu_state *st, bool en) +{ + struct inv_reg_map_s *reg; + u8 data, p; + int result; + reg = &st->reg; + if (en) { + data = INV_CLK_PLL; + p = (BITS_3050_POWER1 | data); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + if (result) + return result; + p = (BITS_3050_POWER2 | data); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + if (result) + return result; + p = data; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + msleep(SENSOR_UP_TIME); + } else { + p = BITS_3050_GYRO_STANDBY; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, p); + } + + return result; +} + +int inv_switch_3050_accel_engine(struct inv_mpu_state *st, bool en) +{ + int result; + if (NULL == st->slave_accel) + return -EPERM; + if (en) + result = st->slave_accel->resume(st); + else + result = st->slave_accel->suspend(st); + + return result; +} + +/** + * inv_init_config_mpu3050() - Initialize hardware, disable FIFO. + * @st: Device driver instance. + * Initial configuration: + * FSR: +/- 2000DPS + * DLPF: 42Hz + * FIFO rate: 50Hz + * Clock source: Gyro PLL + */ +int inv_init_config_mpu3050(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result; + u8 data; + struct inv_mpu_state *st = iio_priv(indio_dev); + + if (st->chip_config.is_asleep) + return -EPERM; + /*reading AUX VDDIO register */ + result = inv_i2c_read(st, REG_3050_AUX_VDDIO, 1, &data); + if (result) + return result; + data &= ~BIT_3050_VDDIO; + if (st->plat_data.level_shifter) + data |= BIT_3050_VDDIO; + result = inv_i2c_single_write(st, REG_3050_AUX_VDDIO, data); + if (result) + return result; + + reg = &st->reg; + /*2000dps full scale range*/ + result = inv_i2c_single_write(st, reg->lpf, + (INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT) + | INV_FILTER_42HZ); + if (result) + return result; + st->chip_config.fsr = INV_FSR_2000DPS; + st->chip_config.lpf = INV_FILTER_42HZ; + result = inv_i2c_single_write(st, reg->sample_rate_div, + ONE_K_HZ/INIT_FIFO_RATE - 1); + if (result) + return result; + st->chip_config.fifo_rate = INIT_FIFO_RATE; + st->irq_dur_ns = INIT_DUR_TIME; + st->chip_config.prog_start_addr = DMP_START_ADDR; + if ((SECONDARY_SLAVE_TYPE_ACCEL == st->plat_data.sec_slave_type) && + st->slave_accel) { + result = st->slave_accel->setup(st); + if (result) + return result; + result = st->slave_accel->set_fs(st, INV_FS_02G); + if (result) + return result; + result = st->slave_accel->set_lpf(st, INIT_FIFO_RATE); + if (result) + return result; + } + + return 0; +} + +/** + * set_power_mpu3050() - set power of mpu3050. + * @st: Device driver instance. + * @power_on: on/off + */ +int set_power_mpu3050(struct inv_mpu_state *st, bool power_on) +{ + struct inv_reg_map_s *reg; + u8 data, p; + int result; + reg = &st->reg; + if (power_on) { + data = 0; + } else { + if (st->slave_accel) { + result = st->slave_accel->suspend(st); + if (result) + return result; + } + data = BIT_SLEEP; + } + if (st->chip_config.gyro_enable) { + p = (BITS_3050_POWER1 | INV_CLK_PLL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + + p = (BITS_3050_POWER2 | INV_CLK_PLL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + + p = INV_CLK_PLL; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data | p); + if (result) + return result; + } else { + data |= (BITS_3050_GYRO_STANDBY | INV_CLK_INTERNAL); + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data); + if (result) + return result; + } + if (power_on) { + msleep(POWER_UP_TIME); + if (st->slave_accel) { + result = st->slave_accel->resume(st); + if (result) + return result; + } + } + st->chip_config.is_asleep = !power_on; + + return 0; +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu_core.c b/drivers/iio/imu/inv_mpu9250/inv_mpu_core.c new file mode 100644 index 0000000000000..9e22cf18a4daa --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu_core.c @@ -0,0 +1,3274 @@ +/* +* 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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" +#include "sysfs.h" +#include "inv_test/inv_counters.h" + +#include +#include +#include +#include + +s64 get_time_ns(void) +{ + struct timespec ts; + ktime_get_ts(&ts); + return timespec_to_ns(&ts); +} + +/* This is for compatibility for power state. Should remove once HAL + does not use power_state sysfs entry */ +static bool fake_asleep; + +static const struct inv_hw_s hw_info[INV_NUM_PARTS] = { + {119, "ITG3500"}, + { 63, "MPU3050"}, + {117, "MPU6050"}, + {118, "MPU9150"}, + {128, "MPU6500"}, + {128, "MPU9250"}, + {128, "MPU9350"}, + {128, "MPU6515"}, +}; + + +static const u8 reg_gyro_offset[] = {REG_XG_OFFS_USRH, + REG_XG_OFFS_USRH + 2, + REG_XG_OFFS_USRH + 4}; + + +const u8 reg_6050_accel_offset[] = {REG_XA_OFFS_H, + REG_XA_OFFS_H + 2, + REG_XA_OFFS_H + 4}; +const u8 reg_6500_accel_offset[] = {REG_6500_XA_OFFS_H, + REG_6500_YA_OFFS_H, + REG_6500_ZA_OFFS_H}; +#ifdef CONFIG_INV_TESTING +static bool suspend_state; +static int inv_mpu_suspend(struct device *dev); +static int inv_mpu_resume(struct device *dev); +struct test_data_out { + bool gyro; + bool accel; + bool compass; + bool pressure; + bool LPQ; + bool SIXQ; + bool PEDQ; +}; +static struct test_data_out data_out_control; +#endif + +static void inv_setup_reg(struct inv_reg_map_s *reg) +{ + reg->sample_rate_div = REG_SAMPLE_RATE_DIV; + reg->lpf = REG_CONFIG; + reg->bank_sel = REG_BANK_SEL; + reg->user_ctrl = REG_USER_CTRL; + reg->fifo_en = REG_FIFO_EN; + reg->gyro_config = REG_GYRO_CONFIG; + reg->accel_config = REG_ACCEL_CONFIG; + reg->fifo_count_h = REG_FIFO_COUNT_H; + reg->fifo_r_w = REG_FIFO_R_W; + reg->raw_accel = REG_RAW_ACCEL; + reg->temperature = REG_TEMPERATURE; + reg->int_enable = REG_INT_ENABLE; + reg->int_status = REG_INT_STATUS; + reg->pwr_mgmt_1 = REG_PWR_MGMT_1; + reg->pwr_mgmt_2 = REG_PWR_MGMT_2; + reg->mem_start_addr = REG_MEM_START_ADDR; + reg->mem_r_w = REG_MEM_RW; + reg->prgm_strt_addrh = REG_PRGM_STRT_ADDRH; +}; + +/** + * inv_i2c_read_base() - Read one or more bytes from the device registers. + * @st: Device driver instance. + * @i2c_addr: i2c address of device. + * @reg: First device register to be read from. + * @length: Number of bytes to read. + * @data: Data read from device. + * NOTE:This is not re-implementation of i2c_smbus_read because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_read_base(struct inv_mpu_state *st, u16 i2c_addr, + u8 reg, u16 length, u8 *data) +{ + struct i2c_msg msgs[2]; + int res; + + if (!data) + return -EINVAL; + + msgs[0].addr = i2c_addr; + msgs[0].flags = 0; /* write */ + msgs[0].buf = ® + msgs[0].len = 1; + + msgs[1].addr = i2c_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = data; + msgs[1].len = length; + + res = i2c_transfer(st->sl_handle, msgs, 2); + + if (res < 2) { + if (res >= 0) + res = -EIO; + } else + res = 0; + + INV_I2C_INC_MPUWRITE(3); + INV_I2C_INC_MPUREAD(length); +#if CONFIG_DYNAMIC_DEBUG + { + char *read = 0; + pr_debug("%s RD%02X%02X%02X -> %s%s\n", st->hw->name, + i2c_addr, reg, length, + wr_pr_debug_begin(data, length, read), + wr_pr_debug_end(read)); + } +#endif + return res; +} + +/** + * inv_i2c_single_write_base() - Write a byte to a device register. + * @st: Device driver instance. + * @i2c_addr: I2C address of the device. + * @reg: Device register to be written to. + * @data: Byte to write to device. + * NOTE:This is not re-implementation of i2c_smbus_write because i2c + * address could be specified in this case. We could have two different + * i2c address due to secondary i2c interface. + */ +int inv_i2c_single_write_base(struct inv_mpu_state *st, + u16 i2c_addr, u8 reg, u8 data) +{ + u8 tmp[2]; + struct i2c_msg msg; + int res; + tmp[0] = reg; + tmp[1] = data; + + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 2; + + pr_debug("%s WR%02X%02X%02X\n", st->hw->name, i2c_addr, reg, data); + INV_I2C_INC_MPUWRITE(3); + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } else + return 0; +} + +static int inv_switch_engine(struct inv_mpu_state *st, bool en, u32 mask) +{ + struct inv_reg_map_s *reg; + u8 data, mgmt_1; + int result; + + reg = &st->reg; + /* Only when gyro is on, can + clock source be switched to gyro. Otherwise, it must be set to + internal clock */ + if (BIT_PWR_GYRO_STBY == mask) { + result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &mgmt_1); + if (result) + return result; + + mgmt_1 &= ~BIT_CLK_MASK; + } + + if ((BIT_PWR_GYRO_STBY == mask) && (!en)) { + /* turning off gyro requires switch to internal clock first. + Then turn off gyro engine */ + mgmt_1 |= INV_CLK_INTERNAL; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, + mgmt_1); + if (result) + return result; + } + + result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &data); + if (result) + return result; + if (en) + data &= (~mask); + else + data |= mask; + result = inv_i2c_single_write(st, reg->pwr_mgmt_2, data); + if (result) + return result; + + if ((BIT_PWR_GYRO_STBY == mask) && en) { + /* only gyro on needs sensor up time */ + msleep(SENSOR_UP_TIME); + /* after gyro is on & stable, switch internal clock to PLL */ + mgmt_1 |= INV_CLK_PLL; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, + mgmt_1); + if (result) + return result; + } + if ((BIT_PWR_ACCEL_STBY == mask) && en) + msleep(REG_UP_TIME); + + return 0; +} + +/* + * inv_lpa_freq() - store current low power frequency setting. + */ +static int inv_lpa_freq(struct inv_mpu_state *st, int lpa_freq) +{ + unsigned long result; + u8 d; + struct inv_reg_map_s *reg; + /* this mapping makes 6500 and 6050 setting close */ + /* 2, 4, 6, 7 corresponds to 0.98, 3.91, 15.63, 31.25 */ + const u8 mpu6500_lpa_mapping[] = {2, 4, 6, 7}; + + if (lpa_freq > MAX_LPA_FREQ_PARAM) + return -EINVAL; + + if (INV_MPU6500 == st->chip_type) { + d = mpu6500_lpa_mapping[lpa_freq]; + result = inv_i2c_single_write(st, REG_6500_LP_ACCEL_ODR, d); + if (result) + return result; + } else { + reg = &st->reg; + result = inv_i2c_read(st, reg->pwr_mgmt_2, 1, &d); + if (result) + return result; + d &= ~BIT_LPA_FREQ; + d |= (u8)(lpa_freq << LPA_FREQ_SHIFT); + result = inv_i2c_single_write(st, reg->pwr_mgmt_2, d); + if (result) + return result; + } + st->chip_config.lpa_freq = lpa_freq; + + return 0; +} + +static int set_power_itg(struct inv_mpu_state *st, bool power_on) +{ + struct inv_reg_map_s *reg; + u8 data; + int result; + + if ((!power_on) == st->chip_config.is_asleep) + return 0; + reg = &st->reg; + if (power_on) + data = 0; + else + data = BIT_SLEEP; + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, data); + if (result) + return result; + + if (power_on) { + if (INV_MPU6500 == st->chip_type) + msleep(POWER_UP_TIME); + else + msleep(REG_UP_TIME); + } + + st->chip_config.is_asleep = !power_on; + + return 0; +} + +/** + * inv_init_config() - Initialize hardware, disable FIFO. + * @indio_dev: Device driver instance. + * Initial configuration: + * FSR: +/- 2000DPS + * DLPF: 42Hz + * FIFO rate: 50Hz + */ +static int inv_init_config(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result, i; + struct inv_mpu_state *st = iio_priv(indio_dev); + const u8 *ch; + u8 d[2]; + + reg = &st->reg; + + result = inv_i2c_single_write(st, reg->gyro_config, + INV_FSR_2000DPS << GYRO_CONFIG_FSR_SHIFT); + if (result) + return result; + + st->chip_config.fsr = INV_FSR_2000DPS; + + result = inv_i2c_single_write(st, reg->lpf, INV_FILTER_42HZ); + if (result) + return result; + st->chip_config.lpf = INV_FILTER_42HZ; + + result = inv_i2c_single_write(st, reg->sample_rate_div, + ONE_K_HZ / INIT_FIFO_RATE - 1); + if (result) + return result; + st->chip_config.fifo_rate = INIT_FIFO_RATE; + st->chip_config.new_fifo_rate = INIT_FIFO_RATE; + st->irq_dur_ns = INIT_DUR_TIME; + st->chip_config.prog_start_addr = DMP_START_ADDR; + if (INV_MPU6050 == st->chip_type) + st->self_test.samples = INIT_ST_MPU6050_SAMPLES; + else + st->self_test.samples = INIT_ST_SAMPLES; + st->self_test.threshold = INIT_ST_THRESHOLD; + st->batch.wake_fifo_on = true; + if (INV_ITG3500 != st->chip_type) { + st->chip_config.accel_fs = INV_FS_02G; + result = inv_i2c_single_write(st, reg->accel_config, + (INV_FS_02G << ACCEL_CONFIG_FSR_SHIFT)); + if (result) + return result; + st->tap.time = INIT_TAP_TIME; + st->tap.thresh = INIT_TAP_THRESHOLD; + st->tap.min_count = INIT_TAP_MIN_COUNT; + st->sample_divider = INIT_SAMPLE_DIVIDER; + st->smd.threshold = MPU_INIT_SMD_THLD; + st->smd.delay = MPU_INIT_SMD_DELAY_THLD; + st->smd.delay2 = MPU_INIT_SMD_DELAY2_THLD; + + result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR, + INIT_MOT_DUR); + if (result) + return result; + st->mot_int.mot_dur = INIT_MOT_DUR; + + result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR, + INIT_MOT_THR); + if (result) + return result; + st->mot_int.mot_thr = INIT_MOT_THR; + + for (i = 0; i < 3; i++) { + result = inv_i2c_read(st, reg_gyro_offset[i], 2, d); + if (result) + return result; + st->rom_gyro_offset[i] = + (short)be16_to_cpup((__be16 *)(d)); + st->input_gyro_offset[i] = 0; + st->input_gyro_dmp_bias[i] = 0; + } + if (INV_MPU6050 == st->chip_type) + ch = reg_6050_accel_offset; + else + ch = reg_6500_accel_offset; + for (i = 0; i < 3; i++) { + result = inv_i2c_read(st, ch[i], 2, d); + if (result) + return result; + st->rom_accel_offset[i] = + (short)be16_to_cpup((__be16 *)(d)); + st->input_accel_offset[i] = 0; + st->input_accel_dmp_bias[i] = 0; + } + st->pedometer_step = 0; + st->pedometer_time = 0; + } + + return 0; +} + +/* + * inv_write_fsr() - Configure the gyro's scale range. + */ +static int inv_write_fsr(struct inv_mpu_state *st, int fsr) +{ + struct inv_reg_map_s *reg; + int result; + + reg = &st->reg; + if ((fsr < 0) || (fsr > MAX_GYRO_FS_PARAM)) + return -EINVAL; + if (fsr == st->chip_config.fsr) + return 0; + + if (INV_MPU3050 == st->chip_type) + result = inv_i2c_single_write(st, reg->lpf, + (fsr << GYRO_CONFIG_FSR_SHIFT) | st->chip_config.lpf); + else + result = inv_i2c_single_write(st, reg->gyro_config, + fsr << GYRO_CONFIG_FSR_SHIFT); + + if (result) + return result; + st->chip_config.fsr = fsr; + + return 0; +} + +/* + * inv_write_accel_fs() - Configure the accelerometer's scale range. + */ +static int inv_write_accel_fs(struct inv_mpu_state *st, int fs) +{ + int result; + struct inv_reg_map_s *reg; + + reg = &st->reg; + if (fs < 0 || fs > MAX_ACCEL_FS_PARAM) + return -EINVAL; + if (fs == st->chip_config.accel_fs) + return 0; + if (INV_MPU3050 == st->chip_type) + result = st->slave_accel->set_fs(st, fs); + else + result = inv_i2c_single_write(st, reg->accel_config, + (fs << ACCEL_CONFIG_FSR_SHIFT)); + if (result) + return result; + + st->chip_config.accel_fs = fs; + + return 0; +} + +static int inv_set_offset_reg(struct inv_mpu_state *st, int reg, int val) +{ + int result; + u8 d; + + d = ((val >> 8) & 0xff); + result = inv_i2c_single_write(st, reg, d); + if (result) + return result; + + d = (val & 0xff); + result = inv_i2c_single_write(st, reg + 1, d); + + return result; +} + +int inv_reset_offset_reg(struct inv_mpu_state *st, bool en) +{ + const u8 *ch; + int i, result; + s16 gyro[3], accel[3]; + + if (en) { + for (i = 0; i < 3; i++) { + gyro[i] = st->rom_gyro_offset[i]; + accel[i] = st->rom_accel_offset[i]; + } + } else { + for (i = 0; i < 3; i++) { + gyro[i] = st->rom_gyro_offset[i] + + st->input_gyro_offset[i]; + accel[i] = st->rom_accel_offset[i] + + (st->input_accel_offset[i] << 1); + } + } + if (INV_MPU6050 == st->chip_type) + ch = reg_6050_accel_offset; + else + ch = reg_6500_accel_offset; + + for (i = 0; i < 3; i++) { + result = inv_set_offset_reg(st, reg_gyro_offset[i], gyro[i]); + if (result) + return result; + result = inv_set_offset_reg(st, ch[i], accel[i]); + if (result) + return result; + } + + return 0; +} +/* + * inv_fifo_rate_store() - Set fifo rate. + */ +static int inv_fifo_rate_store(struct inv_mpu_state *st, int fifo_rate) +{ + if ((fifo_rate < MIN_FIFO_RATE) || (fifo_rate > MAX_FIFO_RATE)) + return -EINVAL; + if (fifo_rate == st->chip_config.fifo_rate) + return 0; + + st->irq_dur_ns = NSEC_PER_SEC / fifo_rate; + st->chip_config.new_fifo_rate = fifo_rate; + + return 0; +} + +/* + * inv_reg_dump_show() - Register dump for testing. + */ +static ssize_t inv_reg_dump_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ii; + char data; + ssize_t bytes_printed = 0; + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + for (ii = 0; ii < st->hw->num_reg; ii++) { + /* don't read fifo r/w register */ + if (ii == st->reg.fifo_r_w) + data = 0; + else + inv_i2c_read(st, ii, 1, &data); + bytes_printed += sprintf(buf + bytes_printed, "%#2x: %#2x\n", + ii, data); + } + mutex_unlock(&indio_dev->mlock); + + return bytes_printed; +} + +int write_be32_key_to_mem(struct inv_mpu_state *st, + u32 data, int key) +{ + cpu_to_be32s(&data); + return mem_w_key(key, sizeof(data), (u8 *)&data); +} + +int inv_write_2bytes(struct inv_mpu_state *st, int k, int data) +{ + u8 d[2]; + + if (data < 0 || data > USHRT_MAX) + return -EINVAL; + + d[0] = (u8)((data >> 8) & 0xff); + d[1] = (u8)(data & 0xff); + + return mem_w_key(k, ARRAY_SIZE(d), d); +} + +static ssize_t _dmp_bias_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data, tmp; + + if (!st->chip_config.firmware_loaded) + return -EINVAL; + + if (!st->chip_config.enable) { + result = st->set_power_state(st, true); + if (result) + return result; + } + + result = kstrtoint(buf, 10, &data); + if (result) + goto dmp_bias_store_fail; + switch (this_attr->address) { + case ATTR_DMP_ACCEL_X_DMP_BIAS: + tmp = st->input_accel_dmp_bias[0]; + st->input_accel_dmp_bias[0] = data; + result = inv_set_accel_bias_dmp(st); + if (result) + st->input_accel_dmp_bias[0] = tmp; + break; + case ATTR_DMP_ACCEL_Y_DMP_BIAS: + tmp = st->input_accel_dmp_bias[1]; + st->input_accel_dmp_bias[1] = data; + result = inv_set_accel_bias_dmp(st); + if (result) + st->input_accel_dmp_bias[1] = tmp; + break; + case ATTR_DMP_ACCEL_Z_DMP_BIAS: + tmp = st->input_accel_dmp_bias[2]; + st->input_accel_dmp_bias[2] = data; + result = inv_set_accel_bias_dmp(st); + if (result) + st->input_accel_dmp_bias[2] = tmp; + break; + case ATTR_DMP_GYRO_X_DMP_BIAS: + result = write_be32_key_to_mem(st, data, + KEY_CFG_EXT_GYRO_BIAS_X); + if (result) + goto dmp_bias_store_fail; + st->input_gyro_dmp_bias[0] = data; + break; + case ATTR_DMP_GYRO_Y_DMP_BIAS: + result = write_be32_key_to_mem(st, data, + KEY_CFG_EXT_GYRO_BIAS_Y); + if (result) + goto dmp_bias_store_fail; + st->input_gyro_dmp_bias[1] = data; + break; + case ATTR_DMP_GYRO_Z_DMP_BIAS: + result = write_be32_key_to_mem(st, data, + KEY_CFG_EXT_GYRO_BIAS_Z); + if (result) + goto dmp_bias_store_fail; + st->input_gyro_dmp_bias[2] = data; + break; + default: + break; + } + +dmp_bias_store_fail: + if (!st->chip_config.enable) + result |= st->set_power_state(st, false); + if (result) + return result; + + return count; +} + +static ssize_t inv_dmp_bias_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + + mutex_lock(&indio_dev->mlock); + result = _dmp_bias_store(dev, attr, buf, count); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +static ssize_t _dmp_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, data; + + if (st->chip_config.enable) + return -EBUSY; + if (this_attr->address <= ATTR_DMP_DISPLAY_ORIENTATION_ON) { + if (!st->chip_config.firmware_loaded) + return -EINVAL; + result = st->set_power_state(st, true); + if (result) + return result; + } + + result = kstrtoint(buf, 10, &data); + if (result) + goto dmp_attr_store_fail; + switch (this_attr->address) { + case ATTR_DMP_PED_INT_ON: + result = inv_enable_pedometer_interrupt(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.ped_int_on = !!data; + break; + case ATTR_DMP_PED_ON: + { + result = inv_enable_pedometer(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.ped_on = !!data; + break; + } + case ATTR_DMP_SMD_ENABLE: + result = inv_write_2bytes(st, KEY_SMD_ENABLE, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.smd_enable = !!data; + break; + case ATTR_DMP_SMD_THLD: + if (data < 0 || data > SHRT_MAX) + goto dmp_attr_store_fail; + result = write_be32_key_to_mem(st, data << 16, + KEY_SMD_ACCEL_THLD); + if (result) + goto dmp_attr_store_fail; + st->smd.threshold = data; + break; + case ATTR_DMP_SMD_DELAY_THLD: + if (data < 0 || data > INT_MAX / MPU_DEFAULT_DMP_FREQ) + goto dmp_attr_store_fail; + result = write_be32_key_to_mem(st, data * MPU_DEFAULT_DMP_FREQ, + KEY_SMD_DELAY_THLD); + if (result) + goto dmp_attr_store_fail; + st->smd.delay = data; + break; + case ATTR_DMP_SMD_DELAY_THLD2: + if (data < 0 || data > INT_MAX / MPU_DEFAULT_DMP_FREQ) + goto dmp_attr_store_fail; + result = write_be32_key_to_mem(st, data * MPU_DEFAULT_DMP_FREQ, + KEY_SMD_DELAY2_THLD); + if (result) + goto dmp_attr_store_fail; + st->smd.delay2 = data; + break; + case ATTR_DMP_TAP_ON: + result = inv_enable_tap_dmp(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.tap_on = !!data; + break; + case ATTR_DMP_TAP_THRESHOLD: + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_tap_threshold_dmp(st, data); + if (result) + goto dmp_attr_store_fail; + st->tap.thresh = data; + break; + case ATTR_DMP_TAP_MIN_COUNT: + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_min_taps_dmp(st, data); + if (result) + goto dmp_attr_store_fail; + st->tap.min_count = data; + break; + case ATTR_DMP_TAP_TIME: + if (data < 0 || data > USHRT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + result = inv_set_tap_time_dmp(st, data); + if (result) + goto dmp_attr_store_fail; + st->tap.time = data; + break; + case ATTR_DMP_DISPLAY_ORIENTATION_ON: + result = inv_set_display_orient_interrupt_dmp(st, !!data); + if (result) + goto dmp_attr_store_fail; + st->chip_config.display_orient_on = !!data; + break; + /* from here, power of chip is not turned on */ + case ATTR_DMP_ON: + st->chip_config.dmp_on = !!data; + break; + case ATTR_DMP_INT_ON: + st->chip_config.dmp_int_on = !!data; + break; + case ATTR_DMP_EVENT_INT_ON: + st->chip_config.dmp_event_int_on = !!data; + break; + case ATTR_DMP_STEP_INDICATOR_ON: + st->chip_config.step_indicator_on = !!data; + break; + case ATTR_DMP_BATCHMODE_TIMEOUT: + if (data < 0 || data > INT_MAX) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + st->batch.timeout = data; + break; + case ATTR_DMP_BATCHMODE_WAKE_FIFO_FULL: + st->batch.wake_fifo_on = !!data; + st->batch.overflow_on = 0; + break; + case ATTR_DMP_SIX_Q_ON: + st->sensor[SENSOR_SIXQ].on = !!data; + break; + case ATTR_DMP_SIX_Q_RATE: + if (data > MPU_DEFAULT_DMP_FREQ || data < 0) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + st->sensor[SENSOR_SIXQ].rate = data; + st->sensor[SENSOR_SIXQ].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_SIXQ].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_DMP_LPQ_ON: + st->sensor[SENSOR_LPQ].on = !!data; + break; + case ATTR_DMP_LPQ_RATE: + if (data > MPU_DEFAULT_DMP_FREQ || data < 0) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + st->sensor[SENSOR_LPQ].rate = data; + st->sensor[SENSOR_LPQ].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_LPQ].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_DMP_PED_Q_ON: + st->sensor[SENSOR_PEDQ].on = !!data; + break; + case ATTR_DMP_PED_Q_RATE: + if (data > MPU_DEFAULT_DMP_FREQ || data < 0) { + result = -EINVAL; + goto dmp_attr_store_fail; + } + st->sensor[SENSOR_PEDQ].rate = data; + st->sensor[SENSOR_PEDQ].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_PEDQ].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_DMP_STEP_DETECTOR_ON: + st->sensor[SENSOR_STEP].on = !!data; + break; +#ifdef CONFIG_INV_TESTING + case ATTR_DEBUG_SMD_ENABLE_TESTP1: + { + u8 d[] = {0x42}; + result = st->set_power_state(st, true); + if (result) + goto dmp_attr_store_fail; + result = mem_w_key(KEY_SMD_ENABLE_TESTPT1, ARRAY_SIZE(d), d); + if (result) + goto dmp_attr_store_fail; + } + break; + case ATTR_DEBUG_SMD_ENABLE_TESTP2: + { + u8 d[] = {0x42}; + result = st->set_power_state(st, true); + if (result) + goto dmp_attr_store_fail; + result = mem_w_key(KEY_SMD_ENABLE_TESTPT2, ARRAY_SIZE(d), d); + if (result) + goto dmp_attr_store_fail; + } + break; +#endif + default: + result = -EINVAL; + goto dmp_attr_store_fail; + } + +dmp_attr_store_fail: + if (this_attr->address <= ATTR_DMP_DISPLAY_ORIENTATION_ON) + result |= st->set_power_state(st, false); + if (result) + return result; + + return count; +} + +/* + * inv_dmp_attr_store() - calling this function will store current + * dmp parameter settings + */ +static ssize_t inv_dmp_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + + int result; + + mutex_lock(&indio_dev->mlock); + result = _dmp_attr_store(dev, attr, buf, count); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +static ssize_t inv_attr64_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result; + u64 tmp; + u32 ped; + + mutex_lock(&indio_dev->mlock); + if (!st->chip_config.enable || !st->chip_config.dmp_on) { + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + + switch (this_attr->address) { + case ATTR_DMP_PEDOMETER_STEPS: + result = inv_get_pedometer_steps(st, &ped); + tmp = st->pedometer_step + ped; + break; + case ATTR_DMP_PEDOMETER_TIME: + result = inv_get_pedometer_time(st, &ped); + tmp = st->pedometer_time + ped; + break; + default: + result = -EINVAL; + break; + } + mutex_unlock(&indio_dev->mlock); + if (result) + return -EINVAL; + return sprintf(buf, "%lld\n", tmp); +} + +static ssize_t inv_attr64_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result; + u64 data; + + mutex_lock(&indio_dev->mlock); + if (st->chip_config.enable || (!st->chip_config.firmware_loaded)) { + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + result = st->set_power_state(st, true); + if (result) { + mutex_unlock(&indio_dev->mlock); + return result; + } + result = kstrtoull(buf, 10, &data); + if (result) + goto attr64_store_fail; + switch (this_attr->address) { + case ATTR_DMP_PEDOMETER_STEPS: + result = write_be32_key_to_mem(st, 0, KEY_D_PEDSTD_STEPCTR); + if (result) + goto attr64_store_fail; + st->pedometer_step = data; + break; + case ATTR_DMP_PEDOMETER_TIME: + result = write_be32_key_to_mem(st, 0, KEY_D_PEDSTD_TIMECTR); + if (result) + goto attr64_store_fail; + st->pedometer_time = data; + break; + default: + result = -EINVAL; + break; + } +attr64_store_fail: + mutex_unlock(&indio_dev->mlock); + result = st->set_power_state(st, false); + if (result) + return result; + + return count; +} +/* + * inv_attr_show() - calling this function will show current + * dmp parameters. + */ +static ssize_t inv_attr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int result, axis; + s8 *m; + + switch (this_attr->address) { + + case ATTR_GYRO_SCALE: + { + const s16 gyro_scale[] = {250, 500, 1000, 2000}; + + return sprintf(buf, "%d\n", gyro_scale[st->chip_config.fsr]); + } + case ATTR_ACCEL_SCALE: + { + const s16 accel_scale[] = {2, 4, 8, 16}; + + return sprintf(buf, "%d\n", + accel_scale[st->chip_config.accel_fs] * + st->chip_info.multi); + } + case ATTR_COMPASS_SCALE: + st->slave_compass->get_scale(st, &result); + + return sprintf(buf, "%d\n", result); + case ATTR_ACCEL_X_CALIBBIAS: + case ATTR_ACCEL_Y_CALIBBIAS: + case ATTR_ACCEL_Z_CALIBBIAS: + axis = this_attr->address - ATTR_ACCEL_X_CALIBBIAS; + return sprintf(buf, "%d\n", st->accel_bias[axis] * + st->chip_info.multi); + case ATTR_GYRO_X_CALIBBIAS: + case ATTR_GYRO_Y_CALIBBIAS: + case ATTR_GYRO_Z_CALIBBIAS: + axis = this_attr->address - ATTR_GYRO_X_CALIBBIAS; + return sprintf(buf, "%d\n", st->gyro_bias[axis]); + case ATTR_SELF_TEST_GYRO_SCALE: + return sprintf(buf, "%d\n", SELF_TEST_GYRO_FULL_SCALE); + case ATTR_SELF_TEST_ACCEL_SCALE: + if (INV_MPU6500 == st->chip_type) + return sprintf(buf, "%d\n", SELF_TEST_ACCEL_6500_SCALE); + else + return sprintf(buf, "%d\n", SELF_TEST_ACCEL_FULL_SCALE); + case ATTR_GYRO_X_OFFSET: + case ATTR_GYRO_Y_OFFSET: + case ATTR_GYRO_Z_OFFSET: + axis = this_attr->address - ATTR_GYRO_X_OFFSET; + return sprintf(buf, "%d\n", st->input_gyro_offset[axis]); + case ATTR_ACCEL_X_OFFSET: + case ATTR_ACCEL_Y_OFFSET: + case ATTR_ACCEL_Z_OFFSET: + axis = this_attr->address - ATTR_ACCEL_X_OFFSET; + return sprintf(buf, "%d\n", st->input_accel_offset[axis]); + case ATTR_DMP_ACCEL_X_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_accel_dmp_bias[0]); + case ATTR_DMP_ACCEL_Y_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_accel_dmp_bias[1]); + case ATTR_DMP_ACCEL_Z_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_accel_dmp_bias[2]); + case ATTR_DMP_GYRO_X_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_gyro_dmp_bias[0]); + case ATTR_DMP_GYRO_Y_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_gyro_dmp_bias[1]); + case ATTR_DMP_GYRO_Z_DMP_BIAS: + return sprintf(buf, "%d\n", st->input_gyro_dmp_bias[2]); + case ATTR_DMP_PED_INT_ON: + return sprintf(buf, "%d\n", st->chip_config.ped_int_on); + case ATTR_DMP_PED_ON: + return sprintf(buf, "%d\n", st->chip_config.ped_on); + case ATTR_DMP_SMD_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.smd_enable); + case ATTR_DMP_SMD_THLD: + return sprintf(buf, "%d\n", st->smd.threshold); + case ATTR_DMP_SMD_DELAY_THLD: + return sprintf(buf, "%d\n", st->smd.delay); + case ATTR_DMP_SMD_DELAY_THLD2: + return sprintf(buf, "%d\n", st->smd.delay2); + case ATTR_DMP_TAP_ON: + return sprintf(buf, "%d\n", st->chip_config.tap_on); + case ATTR_DMP_TAP_THRESHOLD: + return sprintf(buf, "%d\n", st->tap.thresh); + case ATTR_DMP_TAP_MIN_COUNT: + return sprintf(buf, "%d\n", st->tap.min_count); + case ATTR_DMP_TAP_TIME: + return sprintf(buf, "%d\n", st->tap.time); + case ATTR_DMP_DISPLAY_ORIENTATION_ON: + return sprintf(buf, "%d\n", + st->chip_config.display_orient_on); + case ATTR_DMP_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_on); + case ATTR_DMP_INT_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_int_on); + case ATTR_DMP_EVENT_INT_ON: + return sprintf(buf, "%d\n", st->chip_config.dmp_event_int_on); + case ATTR_DMP_STEP_INDICATOR_ON: + return sprintf(buf, "%d\n", st->chip_config.step_indicator_on); + case ATTR_DMP_BATCHMODE_TIMEOUT: + return sprintf(buf, "%d\n", + st->batch.timeout); + case ATTR_DMP_BATCHMODE_WAKE_FIFO_FULL: + return sprintf(buf, "%d\n", + st->batch.wake_fifo_on); + case ATTR_DMP_SIX_Q_ON: + return sprintf(buf, "%d\n", st->sensor[SENSOR_SIXQ].on); + case ATTR_DMP_SIX_Q_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_SIXQ].rate); + case ATTR_DMP_LPQ_ON: + return sprintf(buf, "%d\n", st->sensor[SENSOR_LPQ].on); + case ATTR_DMP_LPQ_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_LPQ].rate); + case ATTR_DMP_PED_Q_ON: + return sprintf(buf, "%d\n", st->sensor[SENSOR_PEDQ].on); + case ATTR_DMP_PED_Q_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_PEDQ].rate); + case ATTR_DMP_STEP_DETECTOR_ON: + return sprintf(buf, "%d\n", st->sensor[SENSOR_STEP].on); + case ATTR_MOTION_LPA_ON: + return sprintf(buf, "%d\n", st->mot_int.mot_on); + case ATTR_MOTION_LPA_FREQ:{ + const char *f[] = {"1.25", "5", "20", "40"}; + return sprintf(buf, "%s\n", f[st->chip_config.lpa_freq]); + } + case ATTR_MOTION_LPA_DURATION: + return sprintf(buf, "%d\n", st->mot_int.mot_dur); + case ATTR_MOTION_LPA_THRESHOLD: + return sprintf(buf, "%d\n", st->mot_int.mot_thr); + + case ATTR_SELF_TEST_SAMPLES: + return sprintf(buf, "%d\n", st->self_test.samples); + case ATTR_SELF_TEST_THRESHOLD: + return sprintf(buf, "%d\n", st->self_test.threshold); + case ATTR_GYRO_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.gyro_enable); + case ATTR_GYRO_FIFO_ENABLE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_GYRO].on); + case ATTR_GYRO_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_GYRO].rate); + case ATTR_ACCEL_ENABLE: + return sprintf(buf, "%d\n", st->chip_config.accel_enable); + case ATTR_ACCEL_FIFO_ENABLE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_ACCEL].on); + case ATTR_ACCEL_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_ACCEL].rate); + case ATTR_COMPASS_ENABLE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_COMPASS].on); + case ATTR_COMPASS_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_COMPASS].rate); + case ATTR_PRESSURE_ENABLE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_PRESSURE].on); + case ATTR_PRESSURE_RATE: + return sprintf(buf, "%d\n", st->sensor[SENSOR_PRESSURE].rate); + case ATTR_POWER_STATE: + return sprintf(buf, "%d\n", !fake_asleep); + case ATTR_FIRMWARE_LOADED: + return sprintf(buf, "%d\n", st->chip_config.firmware_loaded); + case ATTR_SAMPLING_FREQ: + return sprintf(buf, "%d\n", st->chip_config.new_fifo_rate); + case ATTR_SELF_TEST: + mutex_lock(&indio_dev->mlock); + if (st->chip_config.enable) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + if (INV_MPU3050 == st->chip_type) + result = 1; + else + result = inv_hw_self_test(st); + mutex_unlock(&indio_dev->mlock); + return sprintf(buf, "%d\n", result); + case ATTR_GYRO_MATRIX: + m = st->plat_data.orientation; + return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + case ATTR_ACCEL_MATRIX: + if (st->plat_data.sec_slave_type == + SECONDARY_SLAVE_TYPE_ACCEL) + m = + st->plat_data.secondary_orientation; + else + m = st->plat_data.orientation; + return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + case ATTR_COMPASS_MATRIX: + if (st->plat_data.sec_slave_type == + SECONDARY_SLAVE_TYPE_COMPASS) + m = + st->plat_data.secondary_orientation; + else + return -ENODEV; + return sprintf(buf, "%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + case ATTR_SECONDARY_NAME: + { + const char *n[] = {"NULL", "AK8975", "AK8972", "AK8963", + "BMA250", "MLX90399"}; + switch (st->plat_data.sec_slave_id) { + case COMPASS_ID_AK8975: + return sprintf(buf, "%s\n", n[1]); + case COMPASS_ID_AK8972: + return sprintf(buf, "%s\n", n[2]); + case COMPASS_ID_AK8963: + return sprintf(buf, "%s\n", n[3]); + case ACCEL_ID_BMA250: + return sprintf(buf, "%s\n", n[4]); + case COMPASS_ID_MLX90399: + return sprintf(buf, "%s\n", n[5]); + default: + return sprintf(buf, "%s\n", n[0]); + } + } +#ifdef CONFIG_INV_TESTING + case ATTR_REG_WRITE: + return sprintf(buf, "1\n"); + case ATTR_COMPASS_SENS: + { + /* these 2 conditions should never be met, since the + 'compass_sens' sysfs entry should be hidden if the compass + is not an AKM */ + if (st->plat_data.sec_slave_type != + SECONDARY_SLAVE_TYPE_COMPASS) + return -ENODEV; + if (st->plat_data.sec_slave_id != COMPASS_ID_AK8975 && + st->plat_data.sec_slave_id != COMPASS_ID_AK8972 && + st->plat_data.sec_slave_id != COMPASS_ID_AK8963) + return -ENODEV; + m = st->chip_info.compass_sens; + return sprintf(buf, "%d,%d,%d\n", m[0], m[1], m[2]); + } + case ATTR_DEBUG_SMD_EXE_STATE: + { + u8 d[2]; + + result = st->set_power_state(st, true); + mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_SMD_EXE_STATE), 2, d); + return sprintf(buf, "%d\n", (short)be16_to_cpup((__be16 *)(d))); + } + case ATTR_DEBUG_SMD_DELAY_CNTR: + { + u8 d[4]; + + result = st->set_power_state(st, true); + mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_SMD_DELAY_CNTR), 4, d); + return sprintf(buf, "%d\n", (int)be32_to_cpup((__be32 *)(d))); + } +#endif + default: + return -EPERM; + } +} + +/* + * inv_dmp_display_orient_show() - calling this function will + * show orientation This event must use poll. + */ +static ssize_t inv_dmp_display_orient_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_state *st = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", st->display_orient_data); +} + +/* + * inv_accel_motion_show() - calling this function showes motion interrupt. + * This event must use poll. + */ +static ssize_t inv_accel_motion_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "1\n"); +} + +/* + * inv_smd_show() - calling this function showes smd interrupt. + * This event must use poll. + */ +static ssize_t inv_smd_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "1\n"); +} + +/* + * inv_ped_show() - calling this function showes pedometer interrupt. + * This event must use poll. + */ +static ssize_t inv_ped_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "1\n"); +} + +/* + * inv_dmp_tap_show() - calling this function will show tap + * This event must use poll. + */ +static ssize_t inv_dmp_tap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_state *st = iio_priv(dev_get_drvdata(dev)); + return sprintf(buf, "%d\n", st->tap_data); +} + +/* + * inv_temperature_show() - Read temperature data directly from registers. + */ +static ssize_t inv_temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + int result, cur_scale, cur_off; + short temp; + long scale_t; + u8 data[2]; + const long scale[] = {3834792L, 3158064L, 3340827L}; + const long offset[] = {5383314L, 2394184L, 1376256L}; + + reg = &st->reg; + mutex_lock(&indio_dev->mlock); + if (!st->chip_config.enable) + result = st->set_power_state(st, true); + else + result = 0; + if (result) { + mutex_unlock(&indio_dev->mlock); + return result; + } + result = inv_i2c_read(st, reg->temperature, 2, data); + if (!st->chip_config.enable) + result |= st->set_power_state(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) { + pr_err("Could not read temperature register.\n"); + return result; + } + temp = (signed short)(be16_to_cpup((short *)&data[0])); + switch (st->chip_type) { + case INV_MPU3050: + cur_scale = scale[0]; + cur_off = offset[0]; + break; + case INV_MPU6050: + cur_scale = scale[1]; + cur_off = offset[1]; + break; + case INV_MPU6500: + cur_scale = scale[2]; + cur_off = offset[2]; + break; + default: + return -EINVAL; + }; + scale_t = cur_off + + inv_q30_mult((int)temp << MPU_TEMP_SHIFT, cur_scale); + + INV_I2C_INC_TEMPREAD(1); + + return sprintf(buf, "%ld %lld\n", scale_t, get_time_ns()); +} + +static ssize_t inv_flush_batch_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + bool has_data; + + mutex_lock(&indio_dev->mlock); + result = inv_flush_batch_data(indio_dev, &has_data); + mutex_unlock(&indio_dev->mlock); + if (result) + return sprintf(buf, "%d\n", result); + else + return sprintf(buf, "%d\n", has_data); +} + +/* + * inv_firmware_loaded() - calling this function will change + * firmware load + */ +static int inv_firmware_loaded(struct inv_mpu_state *st, int data) +{ + if (data) + return -EINVAL; + st->chip_config.firmware_loaded = 0; + st->chip_config.dmp_on = 0; + + return 0; +} + +static int inv_switch_gyro_engine(struct inv_mpu_state *st, bool en) +{ + return inv_switch_engine(st, en, BIT_PWR_GYRO_STBY); +} + +static int inv_switch_accel_engine(struct inv_mpu_state *st, bool en) +{ + return inv_switch_engine(st, en, BIT_PWR_ACCEL_STBY); +} + +static ssize_t _attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data; + u8 d, axis; + int result; + + result = 0; + if (st->chip_config.enable) + return -EBUSY; + if (this_attr->address <= ATTR_MOTION_LPA_THRESHOLD) { + result = st->set_power_state(st, true); + if (result) + return result; + } + + /* check the input and validate it's format */ + switch (this_attr->address) { +#ifdef CONFIG_INV_TESTING + /* these inputs are strings */ + case ATTR_COMPASS_MATRIX: + case ATTR_COMPASS_SENS: + break; +#endif + /* these inputs are integers */ + default: + result = kstrtoint(buf, 10, &data); + if (result) + goto attr_store_fail; + break; + } + + switch (this_attr->address) { + case ATTR_GYRO_X_OFFSET: + case ATTR_GYRO_Y_OFFSET: + case ATTR_GYRO_Z_OFFSET: + if ((data > MPU_MAX_G_OFFSET_VALUE) || + (data < MPU_MIN_G_OFFSET_VALUE)) + return -EINVAL; + axis = this_attr->address - ATTR_GYRO_X_OFFSET; + result = inv_set_offset_reg(st, + reg_gyro_offset[axis], + st->rom_gyro_offset[axis] + data); + + if (result) + goto attr_store_fail; + st->input_gyro_offset[axis] = data; + break; + case ATTR_ACCEL_X_OFFSET: + case ATTR_ACCEL_Y_OFFSET: + case ATTR_ACCEL_Z_OFFSET: + { + const u8 *ch; + + if ((data > MPU_MAX_A_OFFSET_VALUE) || + (data < MPU_MIN_A_OFFSET_VALUE)) + return -EINVAL; + + axis = this_attr->address - ATTR_ACCEL_X_OFFSET; + if (INV_MPU6050 == st->chip_type) + ch = reg_6050_accel_offset; + else + ch = reg_6500_accel_offset; + + result = inv_set_offset_reg(st, ch[axis], + st->rom_accel_offset[axis] + (data << 1)); + if (result) + goto attr_store_fail; + st->input_accel_offset[axis] = data; + break; + } + case ATTR_GYRO_SCALE: + result = inv_write_fsr(st, data); + break; + case ATTR_ACCEL_SCALE: + result = inv_write_accel_fs(st, data); + break; + case ATTR_COMPASS_SCALE: + result = st->slave_compass->set_scale(st, data); + break; + case ATTR_MOTION_LPA_ON: + if (INV_MPU6500 == st->chip_type) { + if (data) + /* enable and put in MPU6500 mode */ + d = BIT_ACCEL_INTEL_ENABLE + | BIT_ACCEL_INTEL_MODE; + else + d = 0; + result = inv_i2c_single_write(st, + REG_6500_ACCEL_INTEL_CTRL, d); + if (result) + goto attr_store_fail; + } + st->mot_int.mot_on = !!data; + st->chip_config.lpa_mode = !!data; + break; + case ATTR_MOTION_LPA_FREQ: + result = inv_lpa_freq(st, data); + break; + case ATTR_MOTION_LPA_DURATION: + if (INV_MPU6500 != st->chip_type) { + result = inv_i2c_single_write(st, REG_ACCEL_MOT_DUR, + MPU6050_MOTION_DUR_DEFAULT); + if (result) + goto attr_store_fail; + } + st->mot_int.mot_dur = data; + break; + case ATTR_MOTION_LPA_THRESHOLD: + if ((data > MPU6XXX_MAX_MOTION_THRESH) || (data < 0)) { + result = -EINVAL; + goto attr_store_fail; + } + if (INV_MPU6500 == st->chip_type) { + d = (u8)(data >> MPU6500_MOTION_THRESH_SHIFT); + data = (d << MPU6500_MOTION_THRESH_SHIFT); + } else { + d = (u8)(data >> MPU6050_MOTION_THRESH_SHIFT); + data = (d << MPU6050_MOTION_THRESH_SHIFT); + } + + result = inv_i2c_single_write(st, REG_ACCEL_MOT_THR, d); + if (result) + goto attr_store_fail; + st->mot_int.mot_thr = data; + break; + /* from now on, power is not turned on */ + case ATTR_SELF_TEST_SAMPLES: + if (data > ST_MAX_SAMPLES || data < 0) { + result = -EINVAL; + goto attr_store_fail; + } + st->self_test.samples = data; + break; + case ATTR_SELF_TEST_THRESHOLD: + if (data > ST_MAX_THRESHOLD || data < 0) { + result = -EINVAL; + goto attr_store_fail; + } + st->self_test.threshold = data; + case ATTR_GYRO_ENABLE: + st->chip_config.gyro_enable = !!data; + break; + case ATTR_GYRO_FIFO_ENABLE: + st->sensor[SENSOR_GYRO].on = !!data; + break; + case ATTR_GYRO_RATE: + st->sensor[SENSOR_GYRO].rate = data; + st->sensor[SENSOR_GYRO].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_GYRO].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_ACCEL_ENABLE: + st->chip_config.accel_enable = !!data; + break; + case ATTR_ACCEL_FIFO_ENABLE: + st->sensor[SENSOR_ACCEL].on = !!data; + break; + case ATTR_ACCEL_RATE: + st->sensor[SENSOR_ACCEL].rate = data; + st->sensor[SENSOR_ACCEL].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_ACCEL].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_COMPASS_ENABLE: + st->sensor[SENSOR_COMPASS].on = !!data; + break; + case ATTR_COMPASS_RATE: + if (data <= 0) { + result = -EINVAL; + goto attr_store_fail; + } + if ((MSEC_PER_SEC / st->slave_compass->rate_scale) < data) + data = MSEC_PER_SEC / st->slave_compass->rate_scale; + + st->sensor[SENSOR_COMPASS].rate = data; + st->sensor[SENSOR_COMPASS].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_COMPASS].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_PRESSURE_ENABLE: + st->sensor[SENSOR_PRESSURE].on = !!data; + break; + case ATTR_PRESSURE_RATE: + if (data <= 0) { + result = -EINVAL; + goto attr_store_fail; + } + if ((MSEC_PER_SEC / st->slave_pressure->rate_scale) < data) + data = MSEC_PER_SEC / st->slave_pressure->rate_scale; + + st->sensor[SENSOR_PRESSURE].rate = data; + st->sensor[SENSOR_PRESSURE].dur = MPU_DEFAULT_DMP_FREQ / data; + st->sensor[SENSOR_PRESSURE].dur *= DMP_INTERVAL_INIT; + break; + case ATTR_POWER_STATE: + fake_asleep = !data; + break; + case ATTR_FIRMWARE_LOADED: + result = inv_firmware_loaded(st, data); + break; + case ATTR_SAMPLING_FREQ: + result = inv_fifo_rate_store(st, data); + break; +#ifdef CONFIG_INV_TESTING + case ATTR_COMPASS_MATRIX: + { + char *str; + __s8 m[9]; + d = 0; + if (st->plat_data.sec_slave_type == SECONDARY_SLAVE_TYPE_NONE) + return -ENODEV; + while ((str = strsep((char **)&buf, ","))) { + if (d >= 9) { + result = -EINVAL; + goto attr_store_fail; + } + result = kstrtoint(str, 10, &data); + if (result) + goto attr_store_fail; + if (data < -1 || data > 1) { + result = -EINVAL; + goto attr_store_fail; + } + m[d] = data; + d++; + } + if (d < 9) { + result = -EINVAL; + goto attr_store_fail; + } + memcpy(st->plat_data.secondary_orientation, m, sizeof(m)); + pr_debug(KERN_INFO + "compass_matrix: %d,%d,%d,%d,%d,%d,%d,%d,%d\n", + m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); + break; + } + case ATTR_COMPASS_SENS: + { + char *str; + __s8 s[3]; + d = 0; + /* these 2 conditions should never be met, since the + 'compass_sens' sysfs entry should be hidden if the compass + is not an AKM */ + if (st->plat_data.sec_slave_type == SECONDARY_SLAVE_TYPE_NONE) + return -ENODEV; + if (st->plat_data.sec_slave_id != COMPASS_ID_AK8975 && + st->plat_data.sec_slave_id != COMPASS_ID_AK8972 && + st->plat_data.sec_slave_id != COMPASS_ID_AK8963) + return -ENODEV; + /* read the input data, expecting 3 comma separated values */ + while ((str = strsep((char **)&buf, ","))) { + if (d >= 3) { + result = -EINVAL; + goto attr_store_fail; + } + result = kstrtoint(str, 10, &data); + if (result) + goto attr_store_fail; + if (data < 0 || data > 255) { + result = -EINVAL; + goto attr_store_fail; + } + s[d] = data; + d++; + } + if (d < 3) { + result = -EINVAL; + goto attr_store_fail; + } + /* store the new compass sensitivity adjustment */ + memcpy(st->chip_info.compass_sens, s, sizeof(s)); + pr_debug(KERN_INFO + "compass_sens: %d,%d,%d\n", s[0], s[1], s[2]); + break; + } +#endif + default: + result = -EINVAL; + goto attr_store_fail; + }; + +attr_store_fail: + if (this_attr->address <= ATTR_MOTION_LPA_THRESHOLD) + result |= st->set_power_state(st, false); + if (result) + return result; + + return count; +} + +/* + * inv_attr_store() - calling this function will store current + * non-dmp parameter settings + */ +static ssize_t inv_attr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + int result; + + mutex_lock(&indio_dev->mlock); + result = _attr_store(dev, attr, buf, count); + mutex_unlock(&indio_dev->mlock); + + return result; +} + +static ssize_t inv_master_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + int data; + int result; + + result = kstrtoint(buf, 10, &data); + if (result) + return result; + + mutex_lock(&indio_dev->mlock); + if (st->chip_config.enable == (!!data)) { + result = count; + goto end_enable; + } + if (!!data) { + result = st->set_power_state(st, true); + if (result) + goto end_enable; + } + result = set_inv_enable(indio_dev, !!data); + if (result) + goto end_enable; + if (!data) { + result = st->set_power_state(st, false); + if (result) + goto end_enable; + } + result = count; + +end_enable: + mutex_unlock(&indio_dev->mlock); + + return result; +} + +static ssize_t inv_master_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct inv_mpu_state *st = iio_priv(dev_get_drvdata(dev)); + + return sprintf(buf, "%d\n", st->chip_config.enable); +} + +#ifdef CONFIG_INV_TESTING +static ssize_t inv_test_suspend_resume_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int data; + int result; + + result = kstrtoint(buf, 10, &data); + if (result) + return result; + if (data) + inv_mpu_suspend(dev); + else + inv_mpu_resume(dev); + suspend_state = !!data; + + return count; +} + +static ssize_t inv_test_suspend_resume_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return sprintf(buf, "%d\n", suspend_state); +} + + +/* + * inv_reg_write_store() - register write command for testing. + * Format: WSRRDD, where RR is the register in hex, + * and DD is the data in hex. + */ +static ssize_t inv_reg_write_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + u32 result; + u8 wreg, wval; + int temp; + char local_buf[10]; + + if ((buf[0] != 'W' && buf[0] != 'w') || + (buf[1] != 'S' && buf[1] != 's')) + return -EINVAL; + if (strlen(buf) < 6) + return -EINVAL; + + strncpy(local_buf, buf, 7); + local_buf[6] = 0; + result = sscanf(&local_buf[4], "%x", &temp); + if (result == 0) + return -EINVAL; + wval = temp; + local_buf[4] = 0; + sscanf(&local_buf[2], "%x", &temp); + if (result == 0) + return -EINVAL; + wreg = temp; + + result = inv_i2c_single_write(st, wreg, wval); + if (result) + return result; + + return count; +} + +static ssize_t inv_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int data; + u8 *m; + int result; + + if (st->chip_config.enable) + return -EBUSY; + result = kstrtoint(buf, 10, &data); + if (result) + return -EINVAL; + + result = st->set_power_state(st, true); + if (result) + return result; + + switch (this_attr->address) { + case ATTR_DEBUG_ACCEL_COUNTER: + { + u8 D[6] = {0xf2, 0xb0, 0x80, 0xc0, 0xc8, 0xc2}; + u8 E[6] = {0xf3, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_01, ARRAY_SIZE(D), m); + data_out_control.accel = !!data; + break; + } + case ATTR_DEBUG_GYRO_COUNTER: + { + u8 D[6] = {0xf2, 0xb0, 0x80, 0xc4, 0xcc, 0xc6}; + u8 E[6] = {0xf3, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_02, ARRAY_SIZE(D), m); + data_out_control.gyro = !!data; + break; + } + case ATTR_DEBUG_COMPASS_COUNTER: + { + u8 D[6] = {0xf2, 0xb0, 0x81, 0xc0, 0xc8, 0xc2}; + u8 E[6] = {0xf3, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_03, ARRAY_SIZE(D), m); + data_out_control.compass = !!data; + break; + } + case ATTR_DEBUG_PRESSURE_COUNTER: + { + u8 D[6] = {0xf2, 0xb0, 0x81, 0xc4, 0xcc, 0xc6}; + u8 E[6] = {0xf3, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_04, ARRAY_SIZE(D), m); + data_out_control.pressure = !!data; + break; + } + case ATTR_DEBUG_LPQ_COUNTER: + { + u8 D[6] = {0xf1, 0xb1, 0x83, 0xc2, 0xc4, 0xc6}; + u8 E[6] = {0xf1, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_05, ARRAY_SIZE(D), m); + data_out_control.LPQ = !!data; + break; + } + case ATTR_DEBUG_SIXQ_COUNTER: + { + u8 D[6] = {0xf1, 0xb1, 0x89, 0xc2, 0xc4, 0xc6}; + u8 E[6] = {0xf1, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_06, ARRAY_SIZE(D), m); + data_out_control.SIXQ = !!data; + break; + } + case ATTR_DEBUG_PEDQ_COUNTER: + { + u8 D[6] = {0xf2, 0xf2, 0x88, 0xc2, 0xc4, 0xc6}; + u8 E[6] = {0xf3, 0xb1, 0x88, 0xc0, 0xc0, 0xc0}; + + if (data) + m = E; + else + m = D; + result = mem_w_key(KEY_TEST_07, ARRAY_SIZE(D), m); + data_out_control.PEDQ = !!data; + break; + } + default: + return -EINVAL; + } + + return count; +} + +static ssize_t inv_test_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + switch (this_attr->address) { + case ATTR_DEBUG_ACCEL_COUNTER: + return sprintf(buf, "%d\n", data_out_control.accel); + case ATTR_DEBUG_GYRO_COUNTER: + return sprintf(buf, "%d\n", data_out_control.gyro); + case ATTR_DEBUG_COMPASS_COUNTER: + return sprintf(buf, "%d\n", data_out_control.compass); + case ATTR_DEBUG_PRESSURE_COUNTER: + return sprintf(buf, "%d\n", data_out_control.pressure); + case ATTR_DEBUG_LPQ_COUNTER: + return sprintf(buf, "%d\n", data_out_control.LPQ); + case ATTR_DEBUG_SIXQ_COUNTER: + return sprintf(buf, "%d\n", data_out_control.SIXQ); + case ATTR_DEBUG_PEDQ_COUNTER: + return sprintf(buf, "%d\n", data_out_control.PEDQ); + default: + return -EINVAL; + } +} + +#endif /* CONFIG_INV_TESTING */ + +static const struct iio_chan_spec inv_mpu_channels[] = { + IIO_CHAN_SOFT_TIMESTAMP(INV_MPU_SCAN_TIMESTAMP), +}; + + +/*constant IIO attribute */ +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL("10 20 50 100 200 500"); + +/* special sysfs */ +static DEVICE_ATTR(reg_dump, S_IRUGO, inv_reg_dump_show, NULL); +static DEVICE_ATTR(temperature, S_IRUGO, inv_temperature_show, NULL); + +/* event based sysfs, needs poll to read */ +static DEVICE_ATTR(event_tap, S_IRUGO, inv_dmp_tap_show, NULL); +static DEVICE_ATTR(event_display_orientation, S_IRUGO, + inv_dmp_display_orient_show, NULL); +static DEVICE_ATTR(event_accel_motion, S_IRUGO, inv_accel_motion_show, NULL); +static DEVICE_ATTR(event_smd, S_IRUGO, inv_smd_show, NULL); +static DEVICE_ATTR(event_pedometer, S_IRUGO, inv_ped_show, NULL); + +/* master enable method */ +static DEVICE_ATTR(master_enable, S_IRUGO | S_IWUSR, inv_master_enable_show, + inv_master_enable_store); + +/* special run time sysfs entry, read only */ +static DEVICE_ATTR(flush_batch, S_IRUGO, inv_flush_batch_show, NULL); + +/* DMP sysfs with power on/off */ +static IIO_DEVICE_ATTR(in_accel_x_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_ACCEL_X_DMP_BIAS); +static IIO_DEVICE_ATTR(in_accel_y_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_ACCEL_Y_DMP_BIAS); +static IIO_DEVICE_ATTR(in_accel_z_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_ACCEL_Z_DMP_BIAS); + +static IIO_DEVICE_ATTR(in_anglvel_x_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_GYRO_X_DMP_BIAS); +static IIO_DEVICE_ATTR(in_anglvel_y_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_GYRO_Y_DMP_BIAS); +static IIO_DEVICE_ATTR(in_anglvel_z_dmp_bias, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_bias_store, ATTR_DMP_GYRO_Z_DMP_BIAS); + +static IIO_DEVICE_ATTR(pedometer_int_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_PED_INT_ON); +static IIO_DEVICE_ATTR(pedometer_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_PED_ON); + +static IIO_DEVICE_ATTR(smd_enable, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_SMD_ENABLE); +static IIO_DEVICE_ATTR(smd_threshold, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_SMD_THLD); +static IIO_DEVICE_ATTR(smd_delay_threshold, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_SMD_DELAY_THLD); +static IIO_DEVICE_ATTR(smd_delay_threshold2, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_SMD_DELAY_THLD2); + +static IIO_DEVICE_ATTR(pedometer_steps, S_IRUGO | S_IWUSR, inv_attr64_show, + inv_attr64_store, ATTR_DMP_PEDOMETER_STEPS); +static IIO_DEVICE_ATTR(pedometer_time, S_IRUGO | S_IWUSR, inv_attr64_show, + inv_attr64_store, ATTR_DMP_PEDOMETER_TIME); + +static IIO_DEVICE_ATTR(tap_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_TAP_ON); +static IIO_DEVICE_ATTR(tap_threshold, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_TAP_THRESHOLD); +static IIO_DEVICE_ATTR(tap_min_count, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_TAP_MIN_COUNT); +static IIO_DEVICE_ATTR(tap_time, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_TAP_TIME); +static IIO_DEVICE_ATTR(display_orientation_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_DISPLAY_ORIENTATION_ON); + +/* DMP sysfs without power on/off */ +static IIO_DEVICE_ATTR(dmp_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_ON); +static IIO_DEVICE_ATTR(dmp_int_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_INT_ON); +static IIO_DEVICE_ATTR(dmp_event_int_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_EVENT_INT_ON); +static IIO_DEVICE_ATTR(step_indicator_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_STEP_INDICATOR_ON); +static IIO_DEVICE_ATTR(batchmode_timeout, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_BATCHMODE_TIMEOUT); +static IIO_DEVICE_ATTR(batchmode_wake_fifo_full_on, S_IRUGO | S_IWUSR, + inv_attr_show, inv_dmp_attr_store, ATTR_DMP_BATCHMODE_WAKE_FIFO_FULL); + +static IIO_DEVICE_ATTR(six_axes_q_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_SIX_Q_ON); +static IIO_DEVICE_ATTR(six_axes_q_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_SIX_Q_RATE); + +static IIO_DEVICE_ATTR(three_axes_q_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_LPQ_ON); +static IIO_DEVICE_ATTR(three_axes_q_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_LPQ_RATE); + +static IIO_DEVICE_ATTR(ped_q_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_PED_Q_ON); +static IIO_DEVICE_ATTR(ped_q_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_PED_Q_RATE); + +static IIO_DEVICE_ATTR(step_detector_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_dmp_attr_store, ATTR_DMP_STEP_DETECTOR_ON); + +/* non DMP sysfs with power on/off */ +static IIO_DEVICE_ATTR(motion_lpa_on, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_LPA_ON); +static IIO_DEVICE_ATTR(motion_lpa_freq, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_LPA_FREQ); +static IIO_DEVICE_ATTR(motion_lpa_duration, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_LPA_DURATION); +static IIO_DEVICE_ATTR(motion_lpa_threshold, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_MOTION_LPA_THRESHOLD); + +static IIO_DEVICE_ATTR(in_accel_scale, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_SCALE); +static IIO_DEVICE_ATTR(in_anglvel_scale, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_SCALE); +static IIO_DEVICE_ATTR(in_magn_scale, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_SCALE); + +static IIO_DEVICE_ATTR(in_anglvel_x_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_X_OFFSET); +static IIO_DEVICE_ATTR(in_anglvel_y_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_Y_OFFSET); +static IIO_DEVICE_ATTR(in_anglvel_z_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_Z_OFFSET); + +static IIO_DEVICE_ATTR(in_accel_x_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_X_OFFSET); +static IIO_DEVICE_ATTR(in_accel_y_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_Y_OFFSET); +static IIO_DEVICE_ATTR(in_accel_z_offset, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_Z_OFFSET); + +/* non DMP sysfs without power on/off */ +static IIO_DEVICE_ATTR(self_test_samples, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_SELF_TEST_SAMPLES); +static IIO_DEVICE_ATTR(self_test_threshold, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_SELF_TEST_THRESHOLD); +static IIO_DEVICE_ATTR(gyro_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_ENABLE); +static IIO_DEVICE_ATTR(gyro_fifo_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_FIFO_ENABLE); +static IIO_DEVICE_ATTR(gyro_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_GYRO_RATE); + +static IIO_DEVICE_ATTR(accel_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_ENABLE); +static IIO_DEVICE_ATTR(accel_fifo_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_FIFO_ENABLE); +static IIO_DEVICE_ATTR(accel_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_ACCEL_RATE); + +static IIO_DEVICE_ATTR(compass_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_ENABLE); +static IIO_DEVICE_ATTR(compass_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_RATE); + +static IIO_DEVICE_ATTR(pressure_enable, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_PRESSURE_ENABLE); +static IIO_DEVICE_ATTR(pressure_rate, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_PRESSURE_RATE); + +static IIO_DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_POWER_STATE); +static IIO_DEVICE_ATTR(firmware_loaded, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_FIRMWARE_LOADED); +static IIO_DEVICE_ATTR(sampling_frequency, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_SAMPLING_FREQ); + +/* show method only sysfs but with power on/off */ +static IIO_DEVICE_ATTR(self_test, S_IRUGO, inv_attr_show, NULL, + ATTR_SELF_TEST); + +/* show method only sysfs */ +static IIO_DEVICE_ATTR(in_accel_x_calibbias, S_IRUGO, inv_attr_show, + NULL, ATTR_ACCEL_X_CALIBBIAS); +static IIO_DEVICE_ATTR(in_accel_y_calibbias, S_IRUGO, inv_attr_show, + NULL, ATTR_ACCEL_Y_CALIBBIAS); +static IIO_DEVICE_ATTR(in_accel_z_calibbias, S_IRUGO, inv_attr_show, + NULL, ATTR_ACCEL_Z_CALIBBIAS); + +static IIO_DEVICE_ATTR(in_anglvel_x_calibbias, S_IRUGO, + inv_attr_show, NULL, ATTR_GYRO_X_CALIBBIAS); +static IIO_DEVICE_ATTR(in_anglvel_y_calibbias, S_IRUGO, + inv_attr_show, NULL, ATTR_GYRO_Y_CALIBBIAS); +static IIO_DEVICE_ATTR(in_anglvel_z_calibbias, S_IRUGO, + inv_attr_show, NULL, ATTR_GYRO_Z_CALIBBIAS); + +static IIO_DEVICE_ATTR(in_anglvel_self_test_scale, S_IRUGO, + inv_attr_show, NULL, ATTR_SELF_TEST_GYRO_SCALE); +static IIO_DEVICE_ATTR(in_accel_self_test_scale, S_IRUGO, + inv_attr_show, NULL, ATTR_SELF_TEST_ACCEL_SCALE); + +static IIO_DEVICE_ATTR(gyro_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_GYRO_MATRIX); +static IIO_DEVICE_ATTR(accel_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_ACCEL_MATRIX); +#ifdef CONFIG_INV_TESTING /* read/write in test mode */ +static IIO_DEVICE_ATTR(compass_matrix, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_MATRIX); +static IIO_DEVICE_ATTR(compass_sens, S_IRUGO | S_IWUSR, inv_attr_show, + inv_attr_store, ATTR_COMPASS_SENS); +#else +static IIO_DEVICE_ATTR(compass_matrix, S_IRUGO, inv_attr_show, NULL, + ATTR_COMPASS_MATRIX); +#endif +static IIO_DEVICE_ATTR(secondary_name, S_IRUGO, inv_attr_show, NULL, + ATTR_SECONDARY_NAME); + +#ifdef CONFIG_INV_TESTING +static IIO_DEVICE_ATTR(reg_write, S_IRUGO | S_IWUSR, inv_attr_show, + inv_reg_write_store, ATTR_REG_WRITE); +/* smd debug related sysfs */ +static IIO_DEVICE_ATTR(debug_smd_enable_testp1, S_IWUSR, NULL, + inv_dmp_attr_store, ATTR_DEBUG_SMD_ENABLE_TESTP1); +static IIO_DEVICE_ATTR(debug_smd_enable_testp2, S_IWUSR, NULL, + inv_dmp_attr_store, ATTR_DEBUG_SMD_ENABLE_TESTP2); +static IIO_DEVICE_ATTR(debug_smd_exe_state, S_IRUGO, inv_attr_show, + NULL, ATTR_DEBUG_SMD_EXE_STATE); +static IIO_DEVICE_ATTR(debug_smd_delay_cntr, S_IRUGO, inv_attr_show, + NULL, ATTR_DEBUG_SMD_DELAY_CNTR); +static DEVICE_ATTR(test_suspend_resume, S_IRUGO | S_IWUSR, + inv_test_suspend_resume_show, inv_test_suspend_resume_store); + +static IIO_DEVICE_ATTR(test_gyro_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_GYRO_COUNTER); +static IIO_DEVICE_ATTR(test_accel_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_ACCEL_COUNTER); +static IIO_DEVICE_ATTR(test_compass_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_COMPASS_COUNTER); +static IIO_DEVICE_ATTR(test_pressure_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_PRESSURE_COUNTER); +static IIO_DEVICE_ATTR(test_LPQ_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_LPQ_COUNTER); +static IIO_DEVICE_ATTR(test_SIXQ_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_SIXQ_COUNTER); +static IIO_DEVICE_ATTR(test_PEDQ_counter, S_IRUGO | S_IWUSR, inv_test_show, + inv_test_store, ATTR_DEBUG_PEDQ_COUNTER); +#endif + +static const struct attribute *inv_gyro_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &dev_attr_reg_dump.attr, + &dev_attr_temperature.attr, + &dev_attr_master_enable.attr, + &iio_dev_attr_in_anglvel_scale.dev_attr.attr, + &iio_dev_attr_in_anglvel_x_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_y_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_z_calibbias.dev_attr.attr, + &iio_dev_attr_in_anglvel_x_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_y_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_z_offset.dev_attr.attr, + &iio_dev_attr_in_anglvel_self_test_scale.dev_attr.attr, + &iio_dev_attr_self_test_samples.dev_attr.attr, + &iio_dev_attr_self_test_threshold.dev_attr.attr, + &iio_dev_attr_gyro_enable.dev_attr.attr, + &iio_dev_attr_gyro_fifo_enable.dev_attr.attr, + &iio_dev_attr_gyro_rate.dev_attr.attr, + &iio_dev_attr_power_state.dev_attr.attr, + &iio_dev_attr_sampling_frequency.dev_attr.attr, + &iio_dev_attr_self_test.dev_attr.attr, + &iio_dev_attr_gyro_matrix.dev_attr.attr, + &iio_dev_attr_secondary_name.dev_attr.attr, +#ifdef CONFIG_INV_TESTING + &iio_dev_attr_reg_write.dev_attr.attr, + &iio_dev_attr_debug_smd_enable_testp1.dev_attr.attr, + &iio_dev_attr_debug_smd_enable_testp2.dev_attr.attr, + &iio_dev_attr_debug_smd_exe_state.dev_attr.attr, + &iio_dev_attr_debug_smd_delay_cntr.dev_attr.attr, + &dev_attr_test_suspend_resume.attr, + &iio_dev_attr_test_gyro_counter.dev_attr.attr, + &iio_dev_attr_test_accel_counter.dev_attr.attr, + &iio_dev_attr_test_compass_counter.dev_attr.attr, + &iio_dev_attr_test_pressure_counter.dev_attr.attr, + &iio_dev_attr_test_LPQ_counter.dev_attr.attr, + &iio_dev_attr_test_SIXQ_counter.dev_attr.attr, + &iio_dev_attr_test_PEDQ_counter.dev_attr.attr, +#endif +}; + +static const struct attribute *inv_mpu6xxx_attributes[] = { + &dev_attr_event_accel_motion.attr, + &dev_attr_event_smd.attr, + &dev_attr_event_pedometer.attr, + &dev_attr_flush_batch.attr, + &iio_dev_attr_in_accel_scale.dev_attr.attr, + &iio_dev_attr_in_accel_x_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_y_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_z_calibbias.dev_attr.attr, + &iio_dev_attr_in_accel_self_test_scale.dev_attr.attr, + &iio_dev_attr_in_accel_x_offset.dev_attr.attr, + &iio_dev_attr_in_accel_y_offset.dev_attr.attr, + &iio_dev_attr_in_accel_z_offset.dev_attr.attr, + &iio_dev_attr_in_accel_x_dmp_bias.dev_attr.attr, + &iio_dev_attr_in_accel_y_dmp_bias.dev_attr.attr, + &iio_dev_attr_in_accel_z_dmp_bias.dev_attr.attr, + &iio_dev_attr_in_anglvel_x_dmp_bias.dev_attr.attr, + &iio_dev_attr_in_anglvel_y_dmp_bias.dev_attr.attr, + &iio_dev_attr_in_anglvel_z_dmp_bias.dev_attr.attr, + &iio_dev_attr_pedometer_int_on.dev_attr.attr, + &iio_dev_attr_pedometer_on.dev_attr.attr, + &iio_dev_attr_pedometer_steps.dev_attr.attr, + &iio_dev_attr_pedometer_time.dev_attr.attr, + &iio_dev_attr_smd_enable.dev_attr.attr, + &iio_dev_attr_smd_threshold.dev_attr.attr, + &iio_dev_attr_smd_delay_threshold.dev_attr.attr, + &iio_dev_attr_smd_delay_threshold2.dev_attr.attr, + &iio_dev_attr_dmp_on.dev_attr.attr, + &iio_dev_attr_dmp_int_on.dev_attr.attr, + &iio_dev_attr_dmp_event_int_on.dev_attr.attr, + &iio_dev_attr_step_indicator_on.dev_attr.attr, + &iio_dev_attr_batchmode_timeout.dev_attr.attr, + &iio_dev_attr_batchmode_wake_fifo_full_on.dev_attr.attr, + &iio_dev_attr_six_axes_q_on.dev_attr.attr, + &iio_dev_attr_six_axes_q_rate.dev_attr.attr, + &iio_dev_attr_three_axes_q_on.dev_attr.attr, + &iio_dev_attr_three_axes_q_rate.dev_attr.attr, + &iio_dev_attr_ped_q_on.dev_attr.attr, + &iio_dev_attr_ped_q_rate.dev_attr.attr, + &iio_dev_attr_step_detector_on.dev_attr.attr, + &iio_dev_attr_motion_lpa_on.dev_attr.attr, + &iio_dev_attr_motion_lpa_freq.dev_attr.attr, + &iio_dev_attr_motion_lpa_threshold.dev_attr.attr, + &iio_dev_attr_accel_enable.dev_attr.attr, + &iio_dev_attr_accel_fifo_enable.dev_attr.attr, + &iio_dev_attr_accel_rate.dev_attr.attr, + &iio_dev_attr_firmware_loaded.dev_attr.attr, + &iio_dev_attr_accel_matrix.dev_attr.attr, +}; + +static const struct attribute *inv_mpu6050_attributes[] = { + &iio_dev_attr_motion_lpa_duration.dev_attr.attr, +}; + +static const struct attribute *inv_tap_attributes[] = { + &dev_attr_event_tap.attr, + &iio_dev_attr_tap_on.dev_attr.attr, + &iio_dev_attr_tap_threshold.dev_attr.attr, + &iio_dev_attr_tap_min_count.dev_attr.attr, + &iio_dev_attr_tap_time.dev_attr.attr, +}; + +static const struct attribute *inv_display_orient_attributes[] = { + &dev_attr_event_display_orientation.attr, + &iio_dev_attr_display_orientation_on.dev_attr.attr, +}; + +static const struct attribute *inv_compass_attributes[] = { + &iio_dev_attr_in_magn_scale.dev_attr.attr, + &iio_dev_attr_compass_enable.dev_attr.attr, + &iio_dev_attr_compass_rate.dev_attr.attr, + &iio_dev_attr_compass_matrix.dev_attr.attr, +}; + +static const struct attribute *inv_akxxxx_attributes[] = { +#ifdef CONFIG_INV_TESTING + &iio_dev_attr_compass_sens.dev_attr.attr, +#endif +}; + +static const struct attribute *inv_pressure_attributes[] = { + &iio_dev_attr_pressure_enable.dev_attr.attr, + &iio_dev_attr_pressure_rate.dev_attr.attr, +}; + +static const struct attribute *inv_mpu3050_attributes[] = { + &iio_dev_attr_accel_enable.dev_attr.attr, + &iio_dev_attr_accel_fifo_enable.dev_attr.attr, + &iio_dev_attr_accel_matrix.dev_attr.attr, +}; + +static struct attribute *inv_attributes[ + ARRAY_SIZE(inv_gyro_attributes) + + ARRAY_SIZE(inv_mpu6xxx_attributes) + + ARRAY_SIZE(inv_mpu6050_attributes) + + ARRAY_SIZE(inv_compass_attributes) + + ARRAY_SIZE(inv_akxxxx_attributes) + + ARRAY_SIZE(inv_pressure_attributes) + + ARRAY_SIZE(inv_tap_attributes) + + ARRAY_SIZE(inv_display_orient_attributes) + + 1 +]; + +static const struct attribute_group inv_attribute_group = { + .name = "mpu", + .attrs = inv_attributes +}; + +static const struct iio_info mpu_info = { + .driver_module = THIS_MODULE, + .attrs = &inv_attribute_group, +}; + +static void inv_setup_func_ptr(struct inv_mpu_state *st) +{ + if (st->chip_type == INV_MPU3050) { + st->set_power_state = set_power_mpu3050; + st->switch_gyro_engine = inv_switch_3050_gyro_engine; + st->switch_accel_engine = inv_switch_3050_accel_engine; + st->init_config = inv_init_config_mpu3050; + st->setup_reg = inv_setup_reg_mpu3050; + } else { + st->set_power_state = set_power_itg; + st->switch_gyro_engine = inv_switch_gyro_engine; + st->switch_accel_engine = inv_switch_accel_engine; + st->init_config = inv_init_config; + st->setup_reg = inv_setup_reg; + } +} + +static int inv_detect_6xxx(struct inv_mpu_state *st) +{ + int result; + u8 d; + + result = inv_i2c_read(st, REG_WHOAMI, 1, &d); + if (result) + return result; + if ((d == MPU6500_ID) || (d == MPU6515_ID)) { + st->chip_type = INV_MPU6500; + strcpy(st->name, "mpu6500"); + } else { + strcpy(st->name, "mpu6050"); + } + + return 0; +} + +static int inv_setup_vddio(struct inv_mpu_state *st) +{ + int result; + u8 data[1]; + + if (INV_MPU6050 == st->chip_type) { + result = inv_i2c_read(st, REG_YGOFFS_TC, 1, data); + if (result) + return result; + data[0] &= ~BIT_I2C_MST_VDDIO; + if (st->plat_data.level_shifter) + data[0] |= BIT_I2C_MST_VDDIO; + /*set up VDDIO register */ + result = inv_i2c_single_write(st, REG_YGOFFS_TC, data[0]); + if (result) + return result; + } + + return 0; +} + +/* + * inv_check_chip_type() - check and setup chip type. + */ +static int inv_check_chip_type(struct inv_mpu_state *st, + const struct i2c_device_id *id) +{ + struct inv_reg_map_s *reg; + int result; + int t_ind; + struct inv_chip_config_s *conf; + struct mpu_platform_data *plat; + + conf = &st->chip_config; + plat = &st->plat_data; + if (!strcmp(id->name, "itg3500")) { + st->chip_type = INV_ITG3500; + } else if (!strcmp(id->name, "mpu3050")) { + st->chip_type = INV_MPU3050; + } else if (!strcmp(id->name, "mpu6050")) { + st->chip_type = INV_MPU6050; + } else if (!strcmp(id->name, "mpu9150")) { + st->chip_type = INV_MPU6050; + plat->sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS; + plat->sec_slave_id = COMPASS_ID_AK8975; + } else if (!strcmp(id->name, "mpu6500")) { + st->chip_type = INV_MPU6500; + } else if (!strcmp(id->name, "mpu9250")) { + st->chip_type = INV_MPU6500; + plat->sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS; + plat->sec_slave_id = COMPASS_ID_AK8963; + } else if (!strcmp(id->name, "mpu6xxx")) { + st->chip_type = INV_MPU6050; + } else if (!strcmp(id->name, "mpu9350")) { + st->chip_type = INV_MPU6500; + plat->sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS; + plat->sec_slave_id = COMPASS_ID_MLX90399; + /* block use of MPU9350 in this build + since it's not production ready */ + pr_err("MPU9350 support is not officially available yet.\n"); + return -EPERM; + } else if (!strcmp(id->name, "mpu6515")) { + st->chip_type = INV_MPU6500; + } else { + return -EPERM; + } + inv_setup_func_ptr(st); + st->hw = &hw_info[st->chip_type]; + reg = &st->reg; + st->setup_reg(reg); + /* reset to make sure previous state are not there */ + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET); + if (result) + return result; + msleep(POWER_UP_TIME); + /* toggle power state */ + result = st->set_power_state(st, false); + if (result) + return result; + + result = st->set_power_state(st, true); + if (result) + return result; + + if (!strcmp(id->name, "mpu6xxx")) { + /* for MPU6500, reading register need more time */ + msleep(POWER_UP_TIME); + result = inv_detect_6xxx(st); + if (result) + return result; + } + + if ((plat->sec_slave_type != SECONDARY_SLAVE_TYPE_NONE) || + (plat->aux_slave_type != SECONDARY_SLAVE_TYPE_NONE)) { + result = inv_setup_vddio(st); + if (result) + return result; + } + + switch (st->chip_type) { + case INV_ITG3500: + break; + case INV_MPU6050: + case INV_MPU6500: + if (SECONDARY_SLAVE_TYPE_COMPASS == plat->sec_slave_type) + conf->has_compass = 1; + else + conf->has_compass = 0; + if (SECONDARY_SLAVE_TYPE_PRESSURE == plat->aux_slave_type) + conf->has_pressure = 1; + else + conf->has_pressure = 0; + + break; + case INV_MPU3050: + if (SECONDARY_SLAVE_TYPE_ACCEL == plat->sec_slave_type) { + if (ACCEL_ID_BMA250 == plat->sec_slave_id) + inv_register_mpu3050_slave(st); + } + break; + default: + result = st->set_power_state(st, false); + return -ENODEV; + } + if (conf->has_compass && conf->has_pressure && + (COMPASS_ID_MLX90399 == plat->sec_slave_id)) { + pr_err("MLX90399 can't share slaves with others\n"); + return -EINVAL; + } + switch (st->chip_type) { + case INV_MPU6050: + result = inv_get_silicon_rev_mpu6050(st); + break; + case INV_MPU6500: + result = inv_get_silicon_rev_mpu6500(st); + break; + default: + result = 0; + break; + } + if (result) { + pr_err("read silicon rev error\n"); + st->set_power_state(st, false); + return result; + } + + /* turn off the gyro engine after OTP reading */ + result = st->switch_gyro_engine(st, false); + if (result) + return result; + result = st->switch_accel_engine(st, false); + if (result) + return result; + + if (conf->has_compass) { + result = inv_mpu_setup_compass_slave(st); + if (result) { + pr_err("compass setup failed\n"); + st->set_power_state(st, false); + return result; + } + } + if (conf->has_pressure) { + result = inv_mpu_setup_pressure_slave(st); + if (result) { + pr_err("pressure setup failed\n"); + st->set_power_state(st, false); + return result; + } + } + + t_ind = 0; + memcpy(&inv_attributes[t_ind], inv_gyro_attributes, + sizeof(inv_gyro_attributes)); + t_ind += ARRAY_SIZE(inv_gyro_attributes); + + if (INV_MPU3050 == st->chip_type && st->slave_accel != NULL) { + memcpy(&inv_attributes[t_ind], inv_mpu3050_attributes, + sizeof(inv_mpu3050_attributes)); + t_ind += ARRAY_SIZE(inv_mpu3050_attributes); + inv_attributes[t_ind] = NULL; + return 0; + } + + /* all MPU6xxx based parts */ + if ((INV_MPU6050 == st->chip_type) || (INV_MPU6500 == st->chip_type)) { + memcpy(&inv_attributes[t_ind], inv_mpu6xxx_attributes, + sizeof(inv_mpu6xxx_attributes)); + t_ind += ARRAY_SIZE(inv_mpu6xxx_attributes); + memcpy(&inv_attributes[t_ind], inv_tap_attributes, + sizeof(inv_tap_attributes)); + t_ind += ARRAY_SIZE(inv_tap_attributes); + } + + /* MPU6050 only */ + if (INV_MPU6050 == st->chip_type) { + memcpy(&inv_attributes[t_ind], inv_mpu6050_attributes, + sizeof(inv_mpu6050_attributes)); + t_ind += ARRAY_SIZE(inv_mpu6050_attributes); + } + + if (conf->has_compass) { + memcpy(&inv_attributes[t_ind], inv_compass_attributes, + sizeof(inv_compass_attributes)); + t_ind += ARRAY_SIZE(inv_compass_attributes); + + /* AKM only */ + if (st->plat_data.sec_slave_id == COMPASS_ID_AK8975 || + st->plat_data.sec_slave_id == COMPASS_ID_AK8972 || + st->plat_data.sec_slave_id == COMPASS_ID_AK8963) { + memcpy(&inv_attributes[t_ind], inv_akxxxx_attributes, + sizeof(inv_akxxxx_attributes)); + t_ind += ARRAY_SIZE(inv_akxxxx_attributes); + } + } + + if (conf->has_pressure) { + memcpy(&inv_attributes[t_ind], inv_pressure_attributes, + sizeof(inv_pressure_attributes)); + t_ind += ARRAY_SIZE(inv_pressure_attributes); + } + + inv_attributes[t_ind] = NULL; + + return 0; + +} + +/* + * inv_create_dmp_sysfs() - create binary sysfs dmp entry. + */ +static const struct bin_attribute dmp_firmware = { + .attr = { + .name = "dmp_firmware", + .mode = S_IRUGO | S_IWUSR + }, + .size = DMP_IMAGE_SIZE, + .read = inv_dmp_firmware_read, + .write = inv_dmp_firmware_write, +}; + +static const struct bin_attribute six_q_value = { + .attr = { + .name = "six_axes_q_value", + .mode = S_IRUGO | S_IWUSR + }, + .size = QUATERNION_BYTES, + .read = NULL, + .write = inv_six_q_write, +}; + +static int inv_create_dmp_sysfs(struct iio_dev *ind) +{ + int result; + + result = sysfs_create_bin_file(&ind->dev.kobj, &dmp_firmware); + if (result) + return result; + result = sysfs_create_bin_file(&ind->dev.kobj, &six_q_value); + + return result; +} + + +/////////////////////////////////////////////////////////// + +int inv_mpu_power_on(struct mpu_platform_data *pdata) +{ + int err ; + + err = regulator_enable(pdata->vdd_ana); + err = regulator_enable(pdata->vdd_i2c); + pr_debug(KERN_INFO "inv_mpu_power_on call"); + + return err ; + +} + +int inv_mpu_power_off(struct mpu_platform_data *pdata) +{ + int err ; + + err = regulator_disable(pdata->vdd_ana); + err = regulator_disable(pdata->vdd_i2c); + pr_debug(KERN_INFO "inv_mpu_power_off call"); + + return err; +} + +static int inv_parse_orientation_matrix(struct device *dev, s8 *orient) +{ + int rc, i; + struct device_node *np = dev->of_node; + u32 temp_val, temp_val2; + + for (i = 0; i < 9; i++) + orient[i] = 0; + + /* parsing axis x orientation matrix*/ + rc = of_property_read_u32(np, "axis_map_x", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_x\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_x", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_x\n"); + return rc; + } + if (temp_val2) + orient[temp_val] = -1; + else + orient[temp_val] = 1; + + /* parsing axis y orientation matrix*/ + rc = of_property_read_u32(np, "axis_map_y", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_y\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_y", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_y\n"); + return rc; + } + if (temp_val2) + orient[3 + temp_val] = -1; + else + orient[3 + temp_val] = 1; + + /* parsing axis z orientation matrix*/ + rc = of_property_read_u32(np, "axis_map_z", &temp_val); + if (rc) { + dev_err(dev, "Unable to read axis_map_z\n"); + return rc; + } + rc = of_property_read_u32(np, "negate_z", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read negate_z\n"); + return rc; + } + if (temp_val2) + orient[6 + temp_val] = -1; + else + orient[6 + temp_val] = 1; + + return 0; +} + +static int inv_parse_secondary_orientation_matrix(struct device *dev, + s8 *orient) +{ + int rc, i; + struct device_node *np = dev->of_node; + u32 temp_val, temp_val2; + + for (i = 0; i < 9; i++) + orient[i] = 0; + + /* parsing axis x orientation matrix*/ + rc = of_property_read_u32(np, "inven,secondary_axis_map_x", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_x\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_x", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_x\n"); + return rc; + } + if (temp_val2) + orient[temp_val] = -1; + else + orient[temp_val] = 1; + + /* parsing axis y orientation matrix*/ + rc = of_property_read_u32(np, "inven,secondary_axis_map_y", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_y\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_y", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_y\n"); + return rc; + } + if (temp_val2) + orient[3 + temp_val] = -1; + else + orient[3 + temp_val] = 1; + + /* parsing axis z orientation matrix*/ + rc = of_property_read_u32(np, "inven,secondary_axis_map_z", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary axis_map_z\n"); + return rc; + } + rc = of_property_read_u32(np, "inven,secondary_negate_z", &temp_val2); + if (rc) { + dev_err(dev, "Unable to read secondary negate_z\n"); + return rc; + } + if (temp_val2) + orient[6 + temp_val] = -1; + else + orient[6 + temp_val] = 1; + + return 0; +} + +static int inv_parse_secondary(struct device *dev, struct mpu_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + const char *name; + + if (of_property_read_string(np, "inven,secondary_type", &name)) { + dev_err(dev, "Missing secondary type.\n"); + return -EINVAL; + } + if (!strcmp(name, "compass")) { + pdata->sec_slave_type = SECONDARY_SLAVE_TYPE_COMPASS; + } else if (!strcmp(name, "none")) { + pdata->sec_slave_type = SECONDARY_SLAVE_TYPE_NONE; + return 0; + } else { + return -EINVAL; + } + + if (of_property_read_string(np, "inven,secondary_name", &name)) { + dev_err(dev, "Missing secondary name.\n"); + return -EINVAL; + } + if (!strcmp(name, "ak8963")) + pdata->sec_slave_id = COMPASS_ID_AK8963; + else if (!strcmp(name, "ak8975")) + pdata->sec_slave_id = COMPASS_ID_AK8975; + else if (!strcmp(name, "ak8972")) + pdata->sec_slave_id = COMPASS_ID_AK8972; + else + return -EINVAL; + rc = of_property_read_u32(np, "inven,secondary_reg", &temp_val); + if (rc) { + dev_err(dev, "Unable to read secondary register\n"); + return rc; + } + pdata->secondary_i2c_addr = temp_val; + rc = inv_parse_secondary_orientation_matrix(dev, + pdata->secondary_orientation); + + return rc; +} + +static int inv_parse_aux(struct device *dev, struct mpu_platform_data *pdata) +{ + int rc; + struct device_node *np = dev->of_node; + u32 temp_val; + const char *name; + + if (of_property_read_string(np, "inven,aux_type", &name)) { + dev_err(dev, "Missing aux type.\n"); + return -EINVAL; + } + if (!strcmp(name, "pressure")) { + pdata->aux_slave_type = SECONDARY_SLAVE_TYPE_PRESSURE; + } else if (!strcmp(name, "none")) { + pdata->aux_slave_type = SECONDARY_SLAVE_TYPE_NONE; + return 0; + } else { + return -EINVAL; + } + + if (of_property_read_string(np, "inven,aux_name", &name)) { + dev_err(dev, "Missing aux name.\n"); + return -EINVAL; + } + if (!strcmp(name, "bmp280")) + pdata->aux_slave_id = PRESSURE_ID_BMP280; + else + return -EINVAL; + + rc = of_property_read_u32(np, "inven,aux_reg", &temp_val); + if (rc) { + dev_err(dev, "Unable to read aux register\n"); + return rc; + } + pdata->aux_i2c_addr = temp_val; + + return 0; +} + +static int invensense_mpu_parse_dt(struct device *dev, struct mpu_platform_data *pdata) +{ + int rc; + pr_debug("Invensense MPU parse_dt started.\n"); + + rc = inv_parse_orientation_matrix(dev, pdata->orientation); + if (rc) + return rc; + + rc = inv_parse_secondary(dev, pdata); + if (rc) + return rc; + + inv_parse_aux(dev, pdata); + + pdata->vdd_ana = regulator_get(dev, "inven,vdd_ana"); + if (IS_ERR(pdata->vdd_ana)) { + rc = PTR_ERR(pdata->vdd_ana); + dev_err(dev, "Regulator get failed vdd_ana-supply rc=%d\n", rc); + return rc; + } + pdata->vdd_i2c = regulator_get(dev, "inven,vcc_i2c"); + if (IS_ERR(pdata->vdd_i2c)) { + rc = PTR_ERR(pdata->vdd_i2c); + dev_err(dev, "Regulator get failed vcc-i2c-supply rc=%d\n", rc); + return rc; + } + pdata->power_on = inv_mpu_power_on; + pdata->power_off = inv_mpu_power_off; + pr_debug("Invensense MPU parse_dt complete.\n"); + return rc; +} + +/////////////////////////////////////////////////////////// + +/* + * inv_mpu_probe() - probe function. + */ +static int inv_mpu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct inv_mpu_state *st; + struct iio_dev *indio_dev; + int result, err; + pr_debug("Invensense MPU probe started.\n"); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + result = -ENOSYS; + pr_err("I2c function error\n"); + goto out_no_free; + } + indio_dev = iio_allocate_device(sizeof(*st)); + if (indio_dev == NULL) { + pr_err("memory allocation failed\n"); + result = -ENOMEM; + goto out_no_free; + } + st = iio_priv(indio_dev); + st->client = client; + st->sl_handle = client->adapter; + st->i2c_addr = client->addr; + result = invensense_mpu_parse_dt(&client->dev, &st->plat_data); + if (result) + goto out_free; + + //Power on device. + if (st->plat_data.power_on) { + err = st->plat_data.power_on(&st->plat_data); + if (err < 0) { + dev_err(&client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + msleep(100); + result = inv_check_chip_type(st, id); + if (result) + goto out_free; + + result = st->init_config(indio_dev); + if (result) { + dev_err(&client->adapter->dev, + "Could not initialize device.\n"); + goto out_free; + } + /* Make state variables available to all _show and _store functions. */ + i2c_set_clientdata(client, indio_dev); + indio_dev->dev.parent = &client->dev; + if (!strcmp(id->name, "mpu6xxx")) + indio_dev->name = st->name; + else + indio_dev->name = id->name; + indio_dev->channels = inv_mpu_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_mpu_channels); + + indio_dev->info = &mpu_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->currentmode = INDIO_DIRECT_MODE; + + result = inv_mpu_configure_ring(indio_dev); + if (result) { + pr_err("configure ring buffer fail\n"); + goto out_free; + } + result = iio_buffer_register(indio_dev, indio_dev->channels, + indio_dev->num_channels); + if (result) { + pr_err("ring buffer register fail\n"); + goto out_unreg_ring; + } + st->irq = client->irq; + result = inv_mpu_probe_trigger(indio_dev); + if (result) { + pr_err("trigger probe fail\n"); + goto out_remove_ring; + } + + /* Tell the i2c counter, we have an IRQ */ + INV_I2C_SETIRQ(IRQ_MPU, client->irq); + + result = iio_device_register(indio_dev); + if (result) { + pr_err("IIO device register fail\n"); + goto out_remove_trigger; + } + + if (INV_MPU6050 == st->chip_type || + INV_MPU6500 == st->chip_type) { + result = inv_create_dmp_sysfs(indio_dev); + if (result) { + pr_err("create dmp sysfs failed\n"); + goto out_unreg_iio; + } + } + INIT_KFIFO(st->timestamps); + spin_lock_init(&st->time_stamp_lock); + + result = inv_power_up_self_test(st); + if (result) + goto out_unreg_iio; + result = inv_do_test(st, 0, st->gyro_bias, st->accel_bias); + if (result) + goto out_unreg_iio; + inv_recover_setting(st); + result = st->set_power_state(st, false); + if (result) { + dev_err(&client->adapter->dev, + "%s could not be turned off.\n", st->hw->name); + goto out_unreg_iio; + } + inv_init_sensor_struct(st); + INIT_DELAYED_WORK(&st->work, batch_step_only_work); + dev_info(&client->dev, "%s is ready to go!\n", + indio_dev->name); + pr_debug("Invensense MPU probe complete.\n"); + + return 0; +out_unreg_iio: + iio_device_unregister(indio_dev); +out_remove_trigger: + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_mpu_remove_trigger(indio_dev); +out_remove_ring: + iio_buffer_unregister(indio_dev); +out_unreg_ring: + inv_mpu_unconfigure_ring(indio_dev); +out_free: + iio_free_device(indio_dev); +out_no_free: + dev_err(&client->adapter->dev, "%s failed %d\n", __func__, result); + pr_debug("Invensense MPU probe error.\n"); + return -EIO; +} + +static void inv_mpu_shutdown(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_state *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + int result; + + reg = &st->reg; + mutex_lock(&indio_dev->mlock); + dev_dbg(&client->adapter->dev, "Shutting down %s...\n", st->hw->name); + + /* reset to make sure previous state are not there */ + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, BIT_H_RESET); + if (result) + dev_err(&client->adapter->dev, "Failed to reset %s\n", + st->hw->name); + msleep(POWER_UP_TIME); + /* turn off power to ensure gyro engine is off */ + result = st->set_power_state(st, false); + if (result) + dev_err(&client->adapter->dev, "Failed to turn off %s\n", + st->hw->name); + mutex_unlock(&indio_dev->mlock); +} + +/* + * inv_mpu_remove() - remove function. + */ +static int inv_mpu_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct inv_mpu_state *st = iio_priv(indio_dev); + + cancel_delayed_work_sync(&st->work); + kfifo_free(&st->timestamps); + iio_device_unregister(indio_dev); + if (indio_dev->modes & INDIO_BUFFER_TRIGGERED) + inv_mpu_remove_trigger(indio_dev); + iio_buffer_unregister(indio_dev); + inv_mpu_unconfigure_ring(indio_dev); + iio_free_device(indio_dev); + + dev_info(&client->adapter->dev, "inv-mpu-iio module removed.\n"); + + return 0; +} + +static int inv_setup_suspend_batchmode(struct iio_dev *indio_dev, bool suspend) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + if (st->chip_config.dmp_on && + st->chip_config.enable && + st->batch.on && + (!st->chip_config.dmp_event_int_on)) { + if (!st->batch.wake_fifo_on) { + st->batch.overflow_on = suspend; + result = inv_i2c_single_write(st, + st->reg.user_ctrl, 0); + if (result) + return result; + result = inv_batchmode_setup(st); + if (result) + return result; + result = inv_reset_fifo(indio_dev); + if (result) + return result; + } + /* turn off data interrupt in suspend mode;turn on resume */ + result = inv_set_interrupt_on_gesture_event(st, suspend); + if (result) + return result; + } + + return 0; +} + +#ifdef CONFIG_PM +static int inv_mpu_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + pr_debug("%s inv_mpu_resume\n", st->hw->name); + mutex_lock(&indio_dev->mlock); + + result = 0; + if (st->chip_config.dmp_on && st->chip_config.enable) { + result = st->set_power_state(st, true); + result |= inv_read_time_and_ticks(st, true); + if (st->chip_config.ped_int_on) + result |= inv_enable_pedometer_interrupt(st, true); + if (st->chip_config.display_orient_on) + result |= inv_set_display_orient_interrupt_dmp(st, + true); + result |= inv_setup_suspend_batchmode(indio_dev, false); + if (st->batch.step_only) + schedule_delayed_work(&st->work, + msecs_to_jiffies(st->batch.timeout)); + } else if (st->chip_config.enable) { + result = st->set_power_state(st, true); + } + + mutex_unlock(&indio_dev->mlock); + + return result; +} + +static int inv_mpu_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + pr_debug("%s inv_mpu_suspend\n", st->hw->name); + mutex_lock(&indio_dev->mlock); + + result = 0; + if (st->chip_config.dmp_on && st->chip_config.enable) { + /* turn off pedometer interrupt during suspend */ + if (st->chip_config.ped_int_on) + result |= inv_enable_pedometer_interrupt(st, false); + /* turn off orientation interrupt during suspend */ + if (st->chip_config.display_orient_on) + result |= inv_set_display_orient_interrupt_dmp(st, + false); + /* setup batch mode related during suspend */ + result = inv_setup_suspend_batchmode(indio_dev, true); + if (st->batch.step_only) + cancel_delayed_work_sync(&st->work); + /* only in DMP non-batch data mode, turn off the power */ + if ((!st->batch.on) && (!st->chip_config.dmp_event_int_on)) + result |= st->set_power_state(st, false); + } else if (st->chip_config.enable) { + /* in non DMP case, just turn off the power */ + result |= st->set_power_state(st, false); + } + + mutex_unlock(&indio_dev->mlock); + + return result; +} +static const struct dev_pm_ops inv_mpu_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(inv_mpu_suspend, inv_mpu_resume) +}; +#define INV_MPU_PMOPS (&inv_mpu_pmops) +#else +#define INV_MPU_PMOPS NULL +#endif /* CONFIG_PM */ + +static const u16 normal_i2c[] = { I2C_CLIENT_END }; +/* device id table is used to identify what device can be + * supported by this driver + */ +static const struct i2c_device_id inv_mpu_id[] = { + {"itg3500", INV_ITG3500}, + {"mpu3050", INV_MPU3050}, + {"mpu6050", INV_MPU6050}, + {"mpu9150", INV_MPU9150}, + {"mpu6500", INV_MPU6500}, + {"mpu9250", INV_MPU9250}, + {"mpu6xxx", INV_MPU6XXX}, + {"mpu9350", INV_MPU9350}, + {"mpu6515", INV_MPU6515}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, inv_mpu_id); + +static struct i2c_driver inv_mpu_driver = { + .class = I2C_CLASS_HWMON, + .probe = inv_mpu_probe, + .remove = inv_mpu_remove, + .shutdown = inv_mpu_shutdown, + .id_table = inv_mpu_id, + .driver = { + .owner = THIS_MODULE, + .name = "inv-mpu-iio", + .pm = INV_MPU_PMOPS, + }, + .address_list = normal_i2c, +}; + +static int __init inv_mpu_init(void) +{ + int result = i2c_add_driver(&inv_mpu_driver); + if (result) { + pr_err("failed\n"); + return result; + } + return 0; +} + +static void __exit inv_mpu_exit(void) +{ + i2c_del_driver(&inv_mpu_driver); +} + +module_init(inv_mpu_init); +module_exit(inv_mpu_exit); + +MODULE_AUTHOR("Invensense Corporation"); +MODULE_DESCRIPTION("Invensense device driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("inv-mpu-iio"); + diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu_iio.h b/drivers/iio/imu/inv_mpu9250/inv_mpu_iio.h new file mode 100644 index 0000000000000..b38d1aef3cb77 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu_iio.h @@ -0,0 +1,1061 @@ +/* +* 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. +*/ + +#ifndef _INV_MPU_IIO_H_ +#define _INV_MPU_IIO_H_ + +#include +#include +#include +#include +#include + +#include "iio.h" +#include "buffer.h" + +#include "dmpKey.h" + +/*register and associated bit definition*/ +#define REG_3050_FIFO_EN 0x12 +#define BITS_3050_ACCEL_OUT 0x0E + +#define REG_3050_AUX_VDDIO 0x13 +#define BIT_3050_VDDIO 0x04 + +#define REG_3050_SLAVE_ADDR 0x14 +#define REG_3050_SAMPLE_RATE_DIV 0x15 +#define REG_3050_LPF 0x16 +#define REG_3050_INT_ENABLE 0x17 +#define REG_3050_AUX_BST_ADDR 0x18 +#define REG_3050_INT_STATUS 0x1A +#define REG_3050_TEMPERATURE 0x1B +#define REG_3050_RAW_GYRO 0x1D +#define REG_3050_AUX_XOUT_H 0x23 +#define REG_3050_FIFO_COUNT_H 0x3A +#define REG_3050_FIFO_R_W 0x3C + +#define REG_3050_USER_CTRL 0x3D +#define BIT_3050_AUX_IF_EN 0x20 +#define BIT_3050_FIFO_RST 0x02 + +#define REG_3050_PWR_MGMT_1 0x3E +#define BITS_3050_POWER1 0x30 +#define BITS_3050_POWER2 0x10 +#define BITS_3050_GYRO_STANDBY 0x38 + +#define REG_3500_OTP 0x0 + +#define REG_YGOFFS_TC 0x1 +#define BIT_I2C_MST_VDDIO 0x80 + +#define REG_XA_OFFS_H 0x6 +#define REG_XA_OFFS_L_TC 0x7 +#define REG_PRODUCT_ID 0xC +#define REG_ST_GCT_X 0xD +#define REG_XG_OFFS_USRH 0x13 +#define REG_SAMPLE_RATE_DIV 0x19 +#define REG_CONFIG 0x1A + +#define REG_GYRO_CONFIG 0x1B +#define BITS_SELF_TEST_EN 0xE0 + +#define REG_ACCEL_CONFIG 0x1C +#define REG_ACCEL_MOT_THR 0x1F +#define REG_ACCEL_MOT_DUR 0x20 + +#define REG_FIFO_EN 0x23 +#define BIT_ACCEL_OUT 0x08 +#define BITS_GYRO_OUT 0x70 + +#define REG_I2C_MST_CTRL 0x24 +#define BIT_WAIT_FOR_ES 0x40 + +#define REG_I2C_SLV0_ADDR 0x25 +#define REG_I2C_SLV0_REG 0x26 +#define REG_I2C_SLV0_CTRL 0x27 +#define REG_I2C_SLV1_ADDR 0x28 +#define REG_I2C_SLV1_REG 0x29 +#define REG_I2C_SLV1_CTRL 0x2A +#define REG_I2C_SLV2_ADDR 0x2B +#define REG_I2C_SLV2_REG 0x2C +#define REG_I2C_SLV2_CTRL 0x2D +#define REG_I2C_SLV3_ADDR 0x2E +#define REG_I2C_SLV3_REG 0x2F +#define REG_I2C_SLV3_CTRL 0x30 +#define REG_I2C_SLV4_CTRL 0x34 + +#define INV_MPU_BIT_SLV_EN 0x80 +#define INV_MPU_BIT_BYTE_SW 0x40 +#define INV_MPU_BIT_REG_DIS 0x20 +#define INV_MPU_BIT_I2C_READ 0x80 + +#define REG_INT_PIN_CFG 0x37 +#define BIT_BYPASS_EN 0x2 + +#define REG_INT_ENABLE 0x38 +#define BIT_DATA_RDY_EN 0x01 +#define BIT_DMP_INT_EN 0x02 +#define BIT_ZMOT_EN 0x20 +#define BIT_MOT_EN 0x40 +#define BIT_6500_WOM_EN 0x40 + +#define REG_DMP_INT_STATUS 0x39 +#define BIT_DMP_INT_CI 0x01 + +#define REG_INT_STATUS 0x3A +#define BIT_MOT_INT 0x40 +#define BIT_ZMOT_INT 0x20 + +#define REG_RAW_ACCEL 0x3B +#define REG_TEMPERATURE 0x41 +#define REG_EXT_SENS_DATA_00 0x49 +#define REG_EXT_SENS_DATA_08 0x51 + +#define REG_ACCEL_INTEL_STATUS 0x61 + +#define INV_MPU_REG_I2C_SLV0_DO 0x63 +#define INV_MPU_REG_I2C_SLV1_DO 0x64 +#define INV_MPU_REG_I2C_SLV2_DO 0x65 +#define INV_MPU_REG_I2C_SLV3_DO 0x66 + +#define REG_I2C_MST_DELAY_CTRL 0x67 +#define BIT_SLV0_DLY_EN 0x01 +#define BIT_SLV1_DLY_EN 0x02 +#define BIT_SLV2_DLY_EN 0x04 +#define BIT_SLV3_DLY_EN 0x08 + +#define REG_USER_CTRL 0x6A +#define BIT_FIFO_RST 0x04 +#define BIT_DMP_RST 0x08 +#define BIT_I2C_MST_EN 0x20 +#define BIT_FIFO_EN 0x40 +#define BIT_DMP_EN 0x80 + +#define REG_PWR_MGMT_1 0x6B +#define BIT_H_RESET 0x80 +#define BIT_SLEEP 0x40 +#define BIT_CYCLE 0x20 +#define BIT_CLK_MASK 0x7 + +#define REG_PWR_MGMT_2 0x6C +#define BIT_PWR_ACCEL_STBY 0x38 +#define BIT_PWR_GYRO_STBY 0x07 +#define BIT_LPA_FREQ 0xC0 + +#define REG_BANK_SEL 0x6D +#define REG_MEM_START_ADDR 0x6E +#define REG_MEM_RW 0x6F +#define REG_PRGM_STRT_ADDRH 0x70 +#define REG_FIFO_COUNT_H 0x72 +#define REG_FIFO_R_W 0x74 +#define REG_WHOAMI 0x75 + +#define REG_6500_XG_ST_DATA 0x0 +#define REG_6500_XA_ST_DATA 0xD +#define REG_6500_XA_OFFS_H 0x77 +#define REG_6500_YA_OFFS_H 0x7A +#define REG_6500_ZA_OFFS_H 0x7D +#define REG_6500_ACCEL_CONFIG2 0x1D +#define BIT_ACCEL_FCHOCIE_B 0x08 +#define BIT_FIFO_SIZE_1K 0x40 + +#define REG_6500_LP_ACCEL_ODR 0x1E +#define REG_6500_ACCEL_WOM_THR 0x1F + +#define REG_6500_ACCEL_INTEL_CTRL 0x69 +#define BIT_ACCEL_INTEL_ENABLE 0x80 +#define BIT_ACCEL_INTEL_MODE 0x40 + +/* data definitions */ +#define DMP_START_ADDR 0x400 +#define DMP_MASK_TAP 0x3f +#define DMP_MASK_DIS_ORIEN 0xC0 +#define DMP_DIS_ORIEN_SHIFT 6 + +#define BYTES_FOR_DMP 8 +#define BYTES_FOR_EVENTS 4 +#define QUATERNION_BYTES 16 +#define BYTES_PER_SENSOR 6 +#define MPU3050_FOOTER_SIZE 2 +#define FIFO_COUNT_BYTE 2 +#define FIFO_THRESHOLD 500 +#define FIFO_SIZE 800 +#define HARDWARE_FIFO_SIZE 1024 +#define MAX_READ_SIZE 64 +#define POWER_UP_TIME 100 +#define SENSOR_UP_TIME 30 +#define REG_UP_TIME 5 +#define INV_MPU_SAMPLE_RATE_CHANGE_STABLE 50 +#define MPU_MEM_BANK_SIZE 256 +#define SELF_TEST_GYRO_FULL_SCALE 250 +#define SELF_TEST_ACCEL_FULL_SCALE 8 +#define SELF_TEST_ACCEL_6500_SCALE 2 + +/* data header defines */ +#define PRESSURE_HDR 0x8000 +#define ACCEL_HDR 0x4000 +#define GYRO_HDR 0x2000 +#define COMPASS_HDR 0x1000 +#define LPQUAT_HDR 0x0800 +#define SIXQUAT_HDR 0x0400 +#define PEDQUAT_HDR 0x0200 +#define STEP_DETECTOR_HDR 0x0100 +#define STEP_INDICATOR_MASK 0xf + +#define MAX_BYTES_PER_SAMPLE 80 +#define MAX_HW_FIFO_BYTES (BYTES_PER_SENSOR * 2) +#define IIO_BUFFER_BYTES 8 +#define HEADERED_NORMAL_BYTES 8 +#define HEADERED_Q_BYTES 16 + +#define MPU6XXX_MAX_MOTION_THRESH (255*4) +#define MPU6050_MOTION_THRESH_SHIFT 5 +#define MPU6500_MOTION_THRESH_SHIFT 2 +#define MPU6050_MOTION_DUR_DEFAULT 1 +#define MPU6050_ID 0x68 +#define MPU6050_MAX_MOTION_DUR 255 +#define MPU_TEMP_SHIFT 16 +#define LPA_FREQ_SHIFT 6 +#define COMPASS_RATE_SCALE 10 +#define MAX_GYRO_FS_PARAM 3 +#define MAX_ACCEL_FS_PARAM 3 +#define MAX_LPA_FREQ_PARAM 3 +#define MPU_MAX_A_OFFSET_VALUE 16383 +#define MPU_MIN_A_OFFSET_VALUE -16384 +#define MPU_MAX_G_OFFSET_VALUE 32767 +#define MPU_MIN_G_OFFSET_VALUE -32767 +#define MPU6XXX_MAX_MPU_MEM (256 * 12) + +#define INIT_MOT_DUR 128 +#define INIT_MOT_THR 128 +#define INIT_ZMOT_DUR 128 +#define INIT_ZMOT_THR 128 +#define INIT_ST_SAMPLES 200 +#define INIT_ST_MPU6050_SAMPLES 600 +#define INIT_ST_THRESHOLD 50 +#define ST_THRESHOLD_MULTIPLIER 10 +#define ST_MAX_SAMPLES 500 +#define ST_MAX_THRESHOLD 100 +#define DMP_INTERVAL_INIT (5 * NSEC_PER_MSEC) +#define DMP_INTERVAL_MIN_ADJ (50 * NSEC_PER_USEC) + +/*---- MPU6500 ----*/ +#define MPU6500_ID 0x70 /* unique WHOAMI */ +#define MPU6515_ID 0x74 /* unique WHOAMI */ +#define MPU6500_PRODUCT_REVISION 1 +#define MPU6500_MEM_REV_ADDR 0x16 +#define INV_MPU_REV_MASK 0x0F +#define MPU6500_REV 2 +#define MPU_DMP_LOAD_START 0x20 + +/*---- MPU9250 ----*/ +#define MPU9250_ID 0x71 /* unique WHOAMI */ + +/* ----MPU9350 ----*/ +#define MPU9350_ID 0x72 + +#define THREE_AXIS 3 +#define GYRO_CONFIG_FSR_SHIFT 3 +#define ACCEL_CONFIG_FSR_SHIFT 3 +#define GYRO_DPS_SCALE 250 +#define MEM_ADDR_PROD_REV 0x6 +#define SOFT_PROD_VER_BYTES 5 +#define CRC_FIRMWARE_SEED 0 +#define SELF_TEST_SUCCESS 1 +#define MS_PER_DMP_TICK 20 +#define DMP_IMAGE_SIZE 2639 + +/* init parameters */ +#define INIT_FIFO_RATE 50 +#define INIT_DMP_OUTPUT_RATE 25 +#define INIT_DUR_TIME (NSEC_PER_SEC / INIT_FIFO_RATE) +#define INIT_TAP_THRESHOLD 100 +#define INIT_TAP_TIME 100 +#define INIT_TAP_MIN_COUNT 2 +#define INIT_SAMPLE_DIVIDER 4 +#define MPU_INIT_SMD_DELAY_THLD 3 +#define MPU_INIT_SMD_DELAY2_THLD 1 +#define MPU_INIT_SMD_THLD 3000 +#define MPU_DEFAULT_DMP_FREQ 200 +#define MPL_PROD_KEY(ver, rev) (ver * 100 + rev) +#define NUM_OF_PROD_REVS (ARRAY_SIZE(prod_rev_map)) +/*---- MPU6050 Silicon Revisions ----*/ +#define MPU_SILICON_REV_A2 1 /* MPU6050A2 Device */ +#define MPU_SILICON_REV_B1 2 /* MPU6050B1 Device */ + +#define BIT_PRFTCH_EN 0x40 +#define BIT_CFG_USER_BANK 0x20 +#define BITS_MEM_SEL 0x1f + +#define TIME_STAMP_TOR 5 +#define MAX_CATCH_UP 5 +#define DEFAULT_ACCEL_TRIM 16384 +#define DEFAULT_GYRO_TRIM 131 +#define MAX_FIFO_RATE 1000 +#define MAX_DMP_OUTPUT_RATE 200 +#define MIN_FIFO_RATE 4 +#define ONE_K_HZ 1000 +#define NS_PER_MS_SHIFT 20 + +/*tap related defines */ +#define INV_TAP 0x08 +#define INV_NUM_TAP_AXES 3 + +#define INV_TAP_AXIS_X_POS 0x20 +#define INV_TAP_AXIS_X_NEG 0x10 +#define INV_TAP_AXIS_Y_POS 0x08 +#define INV_TAP_AXIS_Y_NEG 0x04 +#define INV_TAP_AXIS_Z_POS 0x02 +#define INV_TAP_AXIS_Z_NEG 0x01 +#define INV_TAP_ALL_DIRECTIONS 0x3f + +#define INV_TAP_AXIS_X 0x1 +#define INV_TAP_AXIS_Y 0x2 +#define INV_TAP_AXIS_Z 0x4 + +#define INV_TAP_AXIS_ALL \ + (INV_TAP_AXIS_X | \ + INV_TAP_AXIS_Y | \ + INV_TAP_AXIS_Z) + +#define INT_SRC_TAP 0x01 +#define INT_SRC_DISPLAY_ORIENT 0x08 +#define INT_SRC_SHAKE 0x10 + +#define INV_X_AXIS_INDEX 0x00 +#define INV_Y_AXIS_INDEX 0x01 +#define INV_Z_AXIS_INDEX 0x02 + +#define INV_ELEMENT_1 0x0001 +#define INV_ELEMENT_2 0x0002 +#define INV_ELEMENT_3 0x0004 +#define INV_ELEMENT_4 0x0008 +#define INV_ELEMENT_5 0x0010 +#define INV_ELEMENT_6 0x0020 +#define INV_ELEMENT_7 0x0040 +#define INV_ELEMENT_8 0x0080 +#define INV_ALL 0xFFFF +#define INV_ELEMENT_MASK 0x00FF +#define INV_GYRO_ACC_MASK 0x007E +#define INV_ACCEL_MASK 0x70 +#define INV_GYRO_MASK 0xE + +struct inv_mpu_state; + +/** + * struct inv_reg_map_s - Notable slave registers. + * @sample_rate_div: Divider applied to gyro output rate. + * @lpf: Configures internal LPF. + * @bank_sel: Selects between memory banks. + * @user_ctrl: Enables/resets the FIFO. + * @fifo_en: Determines which data will appear in FIFO. + * @gyro_config: gyro config register. + * @accel_config: accel config register + * @fifo_count_h: Upper byte of FIFO count. + * @fifo_r_w: FIFO register. + * @raw_accel Address of first accel register. + * @temperature temperature register + * @int_enable: Interrupt enable register. + * @int_status: Interrupt flags. + * @pwr_mgmt_1: Controls chip's power state and clock source. + * @pwr_mgmt_2: Controls power state of individual sensors. + * @mem_start_addr: Address of first memory read. + * @mem_r_w: Access to memory. + * @prgm_strt_addrh firmware program start address register + */ +struct inv_reg_map_s { + u8 sample_rate_div; + u8 lpf; + u8 bank_sel; + u8 user_ctrl; + u8 fifo_en; + u8 gyro_config; + u8 accel_config; + u8 fifo_count_h; + u8 fifo_r_w; + u8 raw_accel; + u8 temperature; + u8 int_enable; + u8 int_status; + u8 pwr_mgmt_1; + u8 pwr_mgmt_2; + u8 mem_start_addr; + u8 mem_r_w; + u8 prgm_strt_addrh; +}; + +/* device enum */ +enum inv_devices { + INV_ITG3500, + INV_MPU3050, + INV_MPU6050, + INV_MPU9150, + INV_MPU6500, + INV_MPU9250, + INV_MPU6XXX, + INV_MPU9350, + INV_MPU6515, + INV_NUM_PARTS +}; + +/** + * struct inv_hw_s - Other important hardware information. + * @num_reg: Number of registers on device. + * @name: name of the chip + */ +struct inv_hw_s { + u8 num_reg; + u8 *name; +}; + +/* enum for sensor */ +enum INV_SENSORS { + SENSOR_GYRO = 0, + SENSOR_ACCEL, + SENSOR_COMPASS, + SENSOR_PRESSURE, + SENSOR_STEP, + SENSOR_PEDQ, + SENSOR_SIXQ, + SENSOR_LPQ, + SENSOR_NUM_MAX, + SENSOR_INVALID, +}; + +/** + * struct inv_sensor - information for each sensor. + * @ts: this sensors timestamp. + * @dur: duration between samples in ns. + * @rate: sensor data rate. + * @counter: dmp tick counter corresponding to rate. + * @on: sensor on/off. + * @sample_size: number of bytes for the sensor. + * @send_data: function pointer to send data or not. + * @set_rate: funcition pointer to set data rate. + */ +struct inv_sensor { + u64 ts; + int dur; + int rate; + int counter; + bool on; + u8 sample_size; + int (*send_data)(struct inv_mpu_state *st, bool on); + int (*set_rate)(struct inv_mpu_state *st); +}; + +/** + * struct inv_batch - information batchmode. + * @on: derived variable for batch mode. + * @overflow_on: overflow mode for batchmode. + * @wake_fifo_on: overflow for suspend mode. + * @step_only: mean only step detector data is batched. + * @post_isr_run: mean post isr has runned once. + * @counter: counter for batch mode. + * @timeout: nominal timeout value for batchmode in milliseconds. + */ +struct inv_batch { + bool on; + bool overflow_on; + bool wake_fifo_on; + bool step_only; + bool post_isr_run; + u32 counter; + u32 timeout; +}; + +/** + * struct inv_chip_config_s - Cached chip configuration data. + * @fsr: Full scale range. + * @lpf: Digital low pass filter frequency. + * @accel_fs: accel full scale range. + * @has_footer: MPU3050 specific work around. + * @has_compass: has compass or not. + * @has_pressure: has pressure sensor or not. + * @enable: master enable to enable output + * @accel_enable: enable accel functionality + * @gyro_enable: enable gyro functionality + * @is_asleep: 1 if chip is powered down. + * @dmp_on: dmp is on/off. + * @dmp_int_on: dmp interrupt on/off. + * @step_indicator_on: step indicate bit added to the sensor or not. + * @dmp_event_int_on: dmp event interrupt on/off. + * @firmware_loaded: flag indicate firmware loaded or not. + * @lpa_mod: low power mode. + * @tap_on: tap on/off. + * @display_orient_on: display orientation on/off. + * @normal_compass_measure: discard first compass data after reset. + * @normal_pressure_measure: discard first pressure data after reset. + * @smd_enable: disable/enable SMD function. + * @ped_int_on: pedometer interrupt enable/disable. + * @ped_on: pedometer on/off. + * @adjust_time: flag to indicate whether adjust chip clock or not. + * @smd_triggered: smd is triggered. + * @lpa_freq: low power frequency + * @prog_start_addr: firmware program start address. + * @fifo_rate: current FIFO update rate. + * @new_fifo_rate: set FIFO update rate + * @bytes_per_datum: number of bytes for 1 sample data. + */ +struct inv_chip_config_s { + u32 fsr:2; + u32 lpf:3; + u32 accel_fs:2; + u32 has_footer:1; + u32 has_compass:1; + u32 has_pressure:1; + u32 enable:1; + u32 accel_enable:1; + u32 gyro_enable:1; + u32 is_asleep:1; + u32 dmp_on:1; + u32 dmp_int_on:1; + u32 dmp_event_int_on:1; + u32 step_indicator_on:1; + u32 firmware_loaded:1; + u32 lpa_mode:1; + u32 tap_on:1; + u32 display_orient_on:1; + u32 normal_compass_measure:1; + u32 normal_pressure_measure:1; + u32 smd_enable:1; + u32 ped_int_on:1; + u32 ped_on:1; + u32 adjust_time:1; + u32 smd_triggered:1; + u16 lpa_freq; + u16 prog_start_addr; + u16 fifo_rate; + u16 new_fifo_rate; + u16 bytes_per_datum; +}; + +/** + * struct inv_chip_info_s - Chip related information. + * @product_id: Product id. + * @product_revision: Product revision. + * @silicon_revision: Silicon revision. + * @software_revision: software revision. + * @multi: accel specific multiplier. + * @compass_sens: compass sensitivity. + * @gyro_sens_trim: Gyro sensitivity trim factor. + * @accel_sens_trim: accel sensitivity trim factor. + */ +struct inv_chip_info_s { + u8 product_id; + u8 product_revision; + u8 silicon_revision; + u8 software_revision; + u8 multi; + u8 compass_sens[3]; + u32 gyro_sens_trim; + u32 accel_sens_trim; +}; + +/** + * struct inv_tap structure to store tap data. + * @min_count: minimum taps counted. + * @thresh: tap threshold. + * @time: tap time. + */ +struct inv_tap { + u16 min_count; + u16 thresh; + u16 time; +}; + +/** + * struct accel_mot_int_s structure to store motion interrupt data + * @mot_thr: motion threshold. + * @mot_dur: motion duration. + * @mot_on: flag to indicate motion detection on; + */ +struct accel_mot_int { + u16 mot_thr; + u32 mot_dur; + u8 mot_on:1; +}; + +/** + * struct self_test_setting - self test settables from sysfs + * samples: number of samples used in self test. + * threshold: threshold fail/pass criterion in self test. + * This value is in the percentage multiplied by 100. + * So 14% would be 14. + */ +struct self_test_setting { + u16 samples; + u16 threshold; +}; + +/** + * struct inv_smd significant motion detection structure. + * threshold: accel threshold for motion detection. + * delay: delay time to confirm 2nd motion. + * delay2: delay window parameter. + */ +struct inv_smd { + u32 threshold; + u32 delay; + u32 delay2; +}; + +struct inv_ped { + u64 steps; + u32 time; +}; + +struct inv_mpu_slave; +/** + * struct inv_mpu_state - Driver state variables. + * @chip_config: Cached attribute information. + * @chip_info: Chip information from read-only registers. + * @trig; iio trigger. + * @tap: tap data structure. + * @smd: SMD data structure. + * @ped: pedometer data structure. + * @batch: batchmode data structure. + * @reg: Map of important registers. + * @self_test: self test settings. + * @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. + * @slave_accel: mpu slave handle for accelerometer(MPU3050 only). + * @slave_compass: mpu slave handle for magnetometer. + * @slave_pressure: mpu slave handle for pressure sensor. + * (*set_power_state)(struct inv_mpu_state *, int on): function ptr + * (*switch_gyro_engine)(struct inv_mpu_state *, int on): function ptr + * (*switch_accel_engine)(struct inv_mpu_state *, int on): function ptr + * (*init_config)(struct iio_dev *indio_dev): function ptr + * (*setup_reg)(struct inv_reg_map_s *reg): function ptr + * @timestamps: kfifo queue to store time stamp. + * @irq: irq number store. + * @accel_bias: accel bias store. + * @gyro_bias: gyro bias store. + * @input_gyro_offset[3]: gyro offset from sysfs. + * @input_accel_offset[3]: accel offset from sysfs. + * @input_accel_dmp_bias[3]: accel bias for dmp. + * @input_gyro_dmp_bias[3];: gyro bias for dmp. + * @rom_gyro_bias[3]: gyro bias from sysfs. + * @rom_accel_bias[3]: accel bias from sysfs. + * @fifo_data[6]: fifo data storage. + * @i2c_addr: i2c address. + * @sample_divider: sample divider for dmp. + * @fifo_divider: fifo divider for dmp. + * @display_orient_data:display orient data. + * @tap_data: tap data. + * @bytes_per_sec: bytes per seconds when in DMP mode. + * @left_over[HEADERED_Q_BYTES]: left over bytes storage. + * @left_over_size: left over size. + * @sensor{SENSOR_NUM_MAX]: sensor individual properties. + * @fifo_count: current fifo_count; + * @dmp_counter: dmp_counter; + * @dmp_ticks: DMP ticks past since the previous read. + * @sl_handle: Handle to I2C port. + * @irq_dur_ns: duration between each irq. + * @ts_counter: time stamp counter. + * @dmp_interval: dmp interval. nomial value is 5 ms. + * @dmp_interval_accum: dmp interval accumlater. + * @diff_accumulater: accumlator for the difference of nominal and actual. + * @last_ts: last time stamp. + * @step_detector_base_ts: base time stamp for step detector calculation. + * @prev_ts: previous time stamp. + * @pedometer_step: pedometer steps stored in driver. + * @pedometer_time: pedometer time stored in driver. + * @last_run_time: last time the post ISR runs. + * @work: work only for batchmode when only step is batched. + * @name: name for distiguish MPU6050 and MPU6500 in MPU6XXX. + * @secondary_name: name for the slave device in the secondary I2C. + */ +struct inv_mpu_state { +#define TIMESTAMP_FIFO_SIZE 64 + struct inv_chip_config_s chip_config; + struct inv_chip_info_s chip_info; + struct iio_trigger *trig; + struct inv_tap tap; + struct inv_smd smd; + struct inv_ped ped; + struct inv_batch batch; + struct inv_reg_map_s reg; + struct self_test_setting self_test; + const struct inv_hw_s *hw; + enum inv_devices chip_type; + spinlock_t time_stamp_lock; + struct i2c_client *client; + struct mpu_platform_data plat_data; + struct inv_mpu_slave *slave_accel; + struct inv_mpu_slave *slave_compass; + struct inv_mpu_slave *slave_pressure; + struct accel_mot_int mot_int; + int (*set_power_state)(struct inv_mpu_state *, bool on); + int (*switch_gyro_engine)(struct inv_mpu_state *, bool on); + int (*switch_accel_engine)(struct inv_mpu_state *, bool on); + int (*init_config)(struct iio_dev *indio_dev); + void (*setup_reg)(struct inv_reg_map_s *reg); + DECLARE_KFIFO(timestamps, u64, TIMESTAMP_FIFO_SIZE); + short irq; + int accel_bias[3]; + int gyro_bias[3]; + s16 input_gyro_offset[3]; + s16 input_accel_offset[3]; + int input_accel_dmp_bias[3]; + int input_gyro_dmp_bias[3]; + s16 rom_gyro_offset[3]; + s16 rom_accel_offset[3]; + u8 fifo_data[6]; + u8 i2c_addr; + u8 sample_divider; + u8 fifo_divider; + u8 display_orient_data; + u8 tap_data; + u16 bytes_per_sec; + u8 left_over[HEADERED_Q_BYTES]; + u32 left_over_size; + struct inv_sensor sensor[SENSOR_NUM_MAX]; + u32 fifo_count; + u32 dmp_counter; + u32 dmp_ticks; + void *sl_handle; + u32 irq_dur_ns; + u32 ts_counter; + u32 dmp_interval; + s32 dmp_interval_accum; + s64 diff_accumulater; + u64 last_ts; + u64 step_detector_base_ts; + u64 prev_ts; + u64 pedometer_step; + u64 pedometer_time; + u64 last_run_time; + struct delayed_work work; + u8 name[20]; + u8 secondary_name[20]; +}; + +/* produces an unique identifier for each device based on the + combination of product version and product revision */ +struct prod_rev_map_t { + u16 mpl_product_key; + u8 silicon_rev; + u16 gyro_trim; + u16 accel_trim; +}; + +/** + * struct inv_mpu_slave - MPU slave structure. + * @st_upper: compass self test upper limit. + * @st_lower: compass self test lower limit. + * @scale: compass scale. + * @rate_scale: decide how fast a compass can read. + * @min_read_time: minimum time between each reading. + * @self_test: self test method of the slave. + * @set_scale: set scale of slave + * @get_scale: read scale back of the slave. + * @suspend: suspend operation. + * @resume: resume operation. + * @setup: setup chip. initialization. + * @combine_data: combine raw data into meaningful data. + * @read_data: read external sensor and output + * @get_mode: get current chip mode. + * @set_lpf: set low pass filter. + * @set_fs: set full scale + * @prev_ts: last time it is read. + */ +struct inv_mpu_slave { + const short *st_upper; + const short *st_lower; + int scale; + int rate_scale; + int min_read_time; + int (*self_test)(struct inv_mpu_state *); + int (*set_scale)(struct inv_mpu_state *, int scale); + int (*get_scale)(struct inv_mpu_state *, int *val); + int (*suspend)(struct inv_mpu_state *); + int (*resume)(struct inv_mpu_state *); + int (*setup)(struct inv_mpu_state *); + int (*combine_data)(u8 *in, short *out); + int (*read_data)(struct inv_mpu_state *, short *out); + int (*get_mode)(void); + int (*set_lpf)(struct inv_mpu_state *, int rate); + int (*set_fs)(struct inv_mpu_state *, int fs); + u64 prev_ts; +}; + +/* scan element definition */ +enum inv_mpu_scan { + INV_MPU_SCAN_QUAT_R = 0, + INV_MPU_SCAN_QUAT_X, + INV_MPU_SCAN_QUAT_Y, + INV_MPU_SCAN_QUAT_Z, + INV_MPU_SCAN_ACCEL_X, + INV_MPU_SCAN_ACCEL_Y, + INV_MPU_SCAN_ACCEL_Z, + INV_MPU_SCAN_GYRO_X, + INV_MPU_SCAN_GYRO_Y, + INV_MPU_SCAN_GYRO_Z, + INV_MPU_SCAN_MAGN_X, + INV_MPU_SCAN_MAGN_Y, + INV_MPU_SCAN_MAGN_Z, + INV_MPU_SCAN_TIMESTAMP, +}; + +enum inv_filter_e { + INV_FILTER_256HZ_NOLPF2 = 0, + INV_FILTER_188HZ, + INV_FILTER_98HZ, + INV_FILTER_42HZ, + INV_FILTER_20HZ, + INV_FILTER_10HZ, + INV_FILTER_5HZ, + INV_FILTER_2100HZ_NOLPF, + NUM_FILTER +}; + +enum inv_slave_mode { + INV_MODE_SUSPEND, + INV_MODE_NORMAL, +}; + +/*==== MPU6050B1 MEMORY ====*/ +enum MPU_MEMORY_BANKS { + MEM_RAM_BANK_0 = 0, + MEM_RAM_BANK_1, + MEM_RAM_BANK_2, + MEM_RAM_BANK_3, + MEM_RAM_BANK_4, + MEM_RAM_BANK_5, + MEM_RAM_BANK_6, + MEM_RAM_BANK_7, + MEM_RAM_BANK_8, + MEM_RAM_BANK_9, + MEM_RAM_BANK_10, + MEM_RAM_BANK_11, + MPU_MEM_NUM_RAM_BANKS, + MPU_MEM_OTP_BANK_0 = 16 +}; + +/* IIO attribute address */ +enum MPU_IIO_ATTR_ADDR { + ATTR_DMP_GYRO_X_DMP_BIAS, + ATTR_DMP_GYRO_Y_DMP_BIAS, + ATTR_DMP_GYRO_Z_DMP_BIAS, + ATTR_DMP_ACCEL_X_DMP_BIAS, + ATTR_DMP_ACCEL_Y_DMP_BIAS, + ATTR_DMP_ACCEL_Z_DMP_BIAS, + ATTR_DMP_PED_INT_ON, + ATTR_DMP_PED_ON, + ATTR_DMP_SMD_ENABLE, + ATTR_DMP_SMD_THLD, + ATTR_DMP_SMD_DELAY_THLD, + ATTR_DMP_SMD_DELAY_THLD2, + ATTR_DMP_PEDOMETER_STEPS, + ATTR_DMP_PEDOMETER_TIME, + ATTR_DMP_TAP_ON, + ATTR_DMP_TAP_THRESHOLD, + ATTR_DMP_TAP_MIN_COUNT, + ATTR_DMP_TAP_TIME, + ATTR_DMP_DISPLAY_ORIENTATION_ON, +/* *****above this line, are DMP features, power needs on/off */ +/* *****below this line, are DMP features, no power needed */ + ATTR_DMP_ON, + ATTR_DMP_INT_ON, + ATTR_DMP_EVENT_INT_ON, + ATTR_DMP_STEP_INDICATOR_ON, + ATTR_DMP_BATCHMODE_TIMEOUT, + ATTR_DMP_BATCHMODE_WAKE_FIFO_FULL, + ATTR_DMP_SIX_Q_ON, + ATTR_DMP_SIX_Q_RATE, + ATTR_DMP_LPQ_ON, + ATTR_DMP_LPQ_RATE, + ATTR_DMP_PED_Q_ON, + ATTR_DMP_PED_Q_RATE, + ATTR_DMP_STEP_DETECTOR_ON, + ATTR_DMP_STEP_DETECTOR_RATE, +/* *****above this line, it is all DMP related features */ +/* *****below this line, it is all non-DMP related features */ + ATTR_GYRO_SCALE, + ATTR_ACCEL_SCALE, + ATTR_COMPASS_SCALE, + ATTR_GYRO_X_OFFSET, + ATTR_GYRO_Y_OFFSET, + ATTR_GYRO_Z_OFFSET, + ATTR_ACCEL_X_OFFSET, + ATTR_ACCEL_Y_OFFSET, + ATTR_ACCEL_Z_OFFSET, + ATTR_MOTION_LPA_ON, + ATTR_MOTION_LPA_FREQ, + ATTR_MOTION_LPA_DURATION, + ATTR_MOTION_LPA_THRESHOLD, +/* *****above this line, it is non-DMP, power needs on/off */ +/* *****below this line, it is non-DMP, no needs to on/off power */ + ATTR_SELF_TEST_SAMPLES, + ATTR_SELF_TEST_THRESHOLD, + ATTR_GYRO_ENABLE, + ATTR_GYRO_FIFO_ENABLE, + ATTR_GYRO_RATE, + ATTR_ACCEL_ENABLE, + ATTR_ACCEL_FIFO_ENABLE, + ATTR_ACCEL_RATE, + ATTR_COMPASS_ENABLE, + ATTR_COMPASS_RATE, + ATTR_PRESSURE_ENABLE, + ATTR_PRESSURE_RATE, + ATTR_POWER_STATE, /* this is fake sysfs for compatibility */ + ATTR_FIRMWARE_LOADED, + ATTR_SAMPLING_FREQ, +/* *****below this line, it is attributes only has show methods */ + ATTR_SELF_TEST, /* this has show-only methods needs power on/off */ + ATTR_GYRO_X_CALIBBIAS, + ATTR_GYRO_Y_CALIBBIAS, + ATTR_GYRO_Z_CALIBBIAS, + ATTR_ACCEL_X_CALIBBIAS, + ATTR_ACCEL_Y_CALIBBIAS, + ATTR_ACCEL_Z_CALIBBIAS, + ATTR_SELF_TEST_GYRO_SCALE, + ATTR_SELF_TEST_ACCEL_SCALE, + ATTR_GYRO_MATRIX, + ATTR_ACCEL_MATRIX, + ATTR_COMPASS_MATRIX, + ATTR_SECONDARY_NAME, +#ifdef CONFIG_INV_TESTING + ATTR_COMPASS_SENS, + ATTR_I2C_COUNTERS, + ATTR_REG_WRITE, + ATTR_DEBUG_SMD_ENABLE_TESTP1, + ATTR_DEBUG_SMD_ENABLE_TESTP2, + ATTR_DEBUG_SMD_EXE_STATE, + ATTR_DEBUG_SMD_DELAY_CNTR, + ATTR_DEBUG_GYRO_COUNTER, + ATTR_DEBUG_ACCEL_COUNTER, + ATTR_DEBUG_COMPASS_COUNTER, + ATTR_DEBUG_PRESSURE_COUNTER, + ATTR_DEBUG_LPQ_COUNTER, + ATTR_DEBUG_SIXQ_COUNTER, + ATTR_DEBUG_PEDQ_COUNTER, +#endif +}; + +enum inv_accel_fs_e { + INV_FS_02G = 0, + INV_FS_04G, + INV_FS_08G, + INV_FS_16G, + NUM_ACCEL_FSR +}; + +enum inv_fsr_e { + INV_FSR_250DPS = 0, + INV_FSR_500DPS, + INV_FSR_1000DPS, + INV_FSR_2000DPS, + NUM_FSR +}; + +enum inv_clock_sel_e { + INV_CLK_INTERNAL = 0, + INV_CLK_PLL, + NUM_CLK +}; + +ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, size_t size); +ssize_t inv_dmp_firmware_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count); +ssize_t inv_six_q_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, size_t size); + +int inv_mpu_configure_ring(struct iio_dev *indio_dev); +int inv_mpu_probe_trigger(struct iio_dev *indio_dev); +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev); +void inv_mpu_remove_trigger(struct iio_dev *indio_dev); +int inv_init_config_mpu3050(struct iio_dev *indio_dev); +int inv_get_silicon_rev_mpu6050(struct inv_mpu_state *st); +int inv_get_silicon_rev_mpu6500(struct inv_mpu_state *st); +int set_3050_bypass(struct inv_mpu_state *st, bool enable); +int inv_register_mpu3050_slave(struct inv_mpu_state *st); +void inv_setup_reg_mpu3050(struct inv_reg_map_s *reg); +int inv_switch_3050_gyro_engine(struct inv_mpu_state *st, bool en); +int inv_switch_3050_accel_engine(struct inv_mpu_state *st, bool en); +int set_power_mpu3050(struct inv_mpu_state *st, bool power_on); +int inv_set_interrupt_on_gesture_event(struct inv_mpu_state *st, bool on); +int inv_set_display_orient_interrupt_dmp(struct inv_mpu_state *st, bool on); +u16 inv_dmp_get_address(u16 key); +int inv_q30_mult(int a, int b); +int inv_set_tap_threshold_dmp(struct inv_mpu_state *st, u16 threshold); +int inv_write_2bytes(struct inv_mpu_state *st, int k, int data); +int inv_set_min_taps_dmp(struct inv_mpu_state *st, u16 min_taps); +int inv_set_tap_time_dmp(struct inv_mpu_state *st, u16 time); +int inv_enable_tap_dmp(struct inv_mpu_state *st, bool on); +int inv_i2c_read_base(struct inv_mpu_state *st, u16 i2c_addr, + u8 reg, u16 length, u8 *data); +int inv_i2c_single_write_base(struct inv_mpu_state *st, + u16 i2c_addr, u8 reg, u8 data); +int inv_do_test(struct inv_mpu_state *st, int self_test_flag, + int *gyro_result, int *accel_result); +int inv_hw_self_test(struct inv_mpu_state *st); +void inv_recover_setting(struct inv_mpu_state *st); +int inv_power_up_self_test(struct inv_mpu_state *st); +s64 get_time_ns(void); +int write_be32_key_to_mem(struct inv_mpu_state *st, + u32 data, int key); +int inv_set_accel_bias_dmp(struct inv_mpu_state *st); +int inv_mpu_setup_compass_slave(struct inv_mpu_state *st); +int inv_mpu_setup_pressure_slave(struct inv_mpu_state *st); +int mpu_memory_write(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data); +int mpu_memory_read(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data); +int mpu_memory_write_unaligned(struct inv_mpu_state *st, u16 key, int len, + u8 const *d); +int inv_quaternion_on(struct inv_mpu_state *st, + struct iio_buffer *ring, bool en); +int inv_enable_pedometer_interrupt(struct inv_mpu_state *st, bool en); +int inv_enable_pedometer(struct inv_mpu_state *st, bool en); +int inv_get_pedometer_steps(struct inv_mpu_state *st, u32 *steps); +int inv_get_pedometer_time(struct inv_mpu_state *st, u32 *time); +int inv_reset_fifo(struct iio_dev *indio_dev); +int inv_reset_offset_reg(struct inv_mpu_state *st, bool en); +int inv_batchmode_setup(struct inv_mpu_state *st); +void inv_init_sensor_struct(struct inv_mpu_state *st); +void batch_step_only_work(struct work_struct *work); +int inv_read_time_and_ticks(struct inv_mpu_state *st, bool resume); +int inv_flush_batch_data(struct iio_dev *indio_dev, bool *has_data); +int set_inv_enable(struct iio_dev *indio_dev, bool enable); +/* used to print i2c data using pr_debug */ +char *wr_pr_debug_begin(u8 const *data, u32 len, char *string); +char *wr_pr_debug_end(char *string); + +#define mem_w(a, b, c) \ + mpu_memory_write(st, st->i2c_addr, a, b, c) +#define mem_w_key(key, b, c) mpu_memory_write_unaligned(st, key, b, c) +#define inv_i2c_read(st, reg, len, data) \ + inv_i2c_read_base(st, st->i2c_addr, reg, len, data) +#define inv_i2c_single_write(st, reg, data) \ + inv_i2c_single_write_base(st, st->i2c_addr, reg, data) +#define inv_secondary_read(reg, len, data) \ + inv_i2c_read_base(st, st->plat_data.secondary_i2c_addr, reg, len, data) +#define inv_secondary_write(reg, data) \ + inv_i2c_single_write_base(st, st->plat_data.secondary_i2c_addr, \ + reg, data) +#define inv_aux_read(reg, len, data) \ + inv_i2c_read_base(st, st->plat_data.aux_i2c_addr, reg, len, data) +#define inv_aux_write(reg, data) \ + inv_i2c_single_write_base(st, st->plat_data.aux_i2c_addr, \ + reg, data) + +#endif /* #ifndef _INV_MPU_IIO_H_ */ + diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu_misc.c b/drivers/iio/imu/inv_mpu9250/inv_mpu_misc.c new file mode 100644 index 0000000000000..c092ba7ed656a --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu_misc.c @@ -0,0 +1,2002 @@ +/* +* 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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" +#include "inv_test/inv_counters.h" + +/* DMP defines */ +#define DMP_ORIENTATION_TIME 500 +#define DMP_ORIENTATION_ANGLE 60 +#define DMP_DEFAULT_FIFO_RATE 200 +#define DMP_TAP_SCALE (767603923 / 5) +#define DMP_MULTI_SHIFT 30 +#define DMP_MULTI_TAP_TIME 500 +#define DMP_SHAKE_REJECT_THRESH 100 +#define DMP_SHAKE_REJECT_TIME 10 +#define DMP_SHAKE_REJECT_TIMEOUT 10 +#define DMP_ANGLE_SCALE 15 +#define DMP_PRECISION 1000 +#define DMP_MAX_DIVIDER 4 +#define DMP_MAX_MIN_TAPS 4 +#define DMP_IMAGE_CRC_VALUE 0xa139df62 + +/*--- Test parameters defaults --- */ +#define DEF_OLDEST_SUPP_PROD_REV 8 +#define DEF_OLDEST_SUPP_SW_REV 2 + +/* sample rate */ +#define DEF_SELFTEST_SAMPLE_RATE 0 +/* full scale setting dps */ +#define DEF_SELFTEST_GYRO_FS (0 << 3) +#define DEF_SELFTEST_ACCEL_FS (2 << 3) +#define DEF_SELFTEST_GYRO_SENS (32768 / 250) +/* wait time before collecting data */ +#define DEF_GYRO_WAIT_TIME 10 +#define DEF_ST_STABLE_TIME 200 +#define DEF_ST_6500_STABLE_TIME 20 +#define DEF_GYRO_SCALE 131 +#define DEF_ST_PRECISION 1000 +#define DEF_ST_ACCEL_FS_MG 8000UL +#define DEF_ST_SCALE (1L << 15) +#define DEF_ST_TRY_TIMES 2 +#define DEF_ST_COMPASS_RESULT_SHIFT 2 +#define DEF_ST_ACCEL_RESULT_SHIFT 1 +#define DEF_ST_OTP0_THRESH 60 +#define DEF_ST_ABS_THRESH 20 +#define DEF_ST_TOR 2 + +#define X 0 +#define Y 1 +#define Z 2 +/*---- MPU6050 notable product revisions ----*/ +#define MPU_PRODUCT_KEY_B1_E1_5 105 +#define MPU_PRODUCT_KEY_B2_F1 431 +/* accelerometer Hw self test min and max bias shift (mg) */ +#define DEF_ACCEL_ST_SHIFT_MIN 300 +#define DEF_ACCEL_ST_SHIFT_MAX 950 + +#define DEF_ACCEL_ST_SHIFT_DELTA 140 +#define DEF_GYRO_CT_SHIFT_DELTA 140 +/* gyroscope Coriolis self test min and max bias shift (dps) */ +#define DEF_GYRO_CT_SHIFT_MIN 10 +#define DEF_GYRO_CT_SHIFT_MAX 105 + +/*---- MPU6500 Self Test Pass/Fail Criteria ----*/ +/* Gyro Offset Max Value (dps) */ +#define DEF_GYRO_OFFSET_MAX 20 +/* Gyro Self Test Absolute Limits ST_AL (dps) */ +#define DEF_GYRO_ST_AL 60 +/* Accel Self Test Absolute Limits ST_AL (mg) */ +#define DEF_ACCEL_ST_AL_MIN 225 +#define DEF_ACCEL_ST_AL_MAX 675 +#define DEF_6500_ACCEL_ST_SHIFT_DELTA 500 +#define DEF_6500_GYRO_CT_SHIFT_DELTA 500 +#define DEF_ST_MPU6500_ACCEL_LPF 2 +#define DEF_ST_6500_ACCEL_FS_MG 2000UL +#define DEF_SELFTEST_6500_ACCEL_FS (0 << 3) + +/* Note: The ST_AL values are only used when ST_OTP = 0, + * i.e no factory self test values for reference + */ + +/* NOTE: product entries are in chronological order */ +static const struct prod_rev_map_t prod_rev_map[] = { + /* prod_ver = 0 */ + {MPL_PROD_KEY(0, 1), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 2), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 3), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 4), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 5), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 6), MPU_SILICON_REV_A2, 131, 16384}, + /* prod_ver = 1 */ + {MPL_PROD_KEY(0, 7), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 8), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 9), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 10), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 11), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 12), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 13), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 14), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 15), MPU_SILICON_REV_A2, 131, 16384}, + {MPL_PROD_KEY(0, 27), MPU_SILICON_REV_A2, 131, 16384}, + /* prod_ver = 1 */ + {MPL_PROD_KEY(1, 16), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 17), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 18), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 19), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 20), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 28), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 1), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 2), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 3), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 4), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 5), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(1, 6), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 2 */ + {MPL_PROD_KEY(2, 7), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 8), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 9), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 10), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 11), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 12), MPU_SILICON_REV_B1, 131, 16384}, + {MPL_PROD_KEY(2, 29), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 3 */ + {MPL_PROD_KEY(3, 30), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 4 */ + {MPL_PROD_KEY(4, 31), MPU_SILICON_REV_B1, 131, 8192}, + {MPL_PROD_KEY(4, 1), MPU_SILICON_REV_B1, 131, 8192}, + {MPL_PROD_KEY(4, 3), MPU_SILICON_REV_B1, 131, 8192}, + /* prod_ver = 5 */ + {MPL_PROD_KEY(5, 3), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 6 */ + {MPL_PROD_KEY(6, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 7 */ + {MPL_PROD_KEY(7, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 8 */ + {MPL_PROD_KEY(8, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 9 */ + {MPL_PROD_KEY(9, 19), MPU_SILICON_REV_B1, 131, 16384}, + /* prod_ver = 10 */ + {MPL_PROD_KEY(10, 19), MPU_SILICON_REV_B1, 131, 16384} +}; + +/* +* List of product software revisions +* +* NOTE : +* software revision 0 falls back to the old detection method +* based off the product version and product revision per the +* table above +*/ +static const struct prod_rev_map_t sw_rev_map[] = { + {0, 0, 0, 0}, + {1, MPU_SILICON_REV_B1, 131, 8192}, /* rev C */ + {2, MPU_SILICON_REV_B1, 131, 16384} /* rev D */ +}; + +static const u16 mpu_6500_st_tb[256] = { + 2620, 2646, 2672, 2699, 2726, 2753, 2781, 2808, + 2837, 2865, 2894, 2923, 2952, 2981, 3011, 3041, + 3072, 3102, 3133, 3165, 3196, 3228, 3261, 3293, + 3326, 3359, 3393, 3427, 3461, 3496, 3531, 3566, + 3602, 3638, 3674, 3711, 3748, 3786, 3823, 3862, + 3900, 3939, 3979, 4019, 4059, 4099, 4140, 4182, + 4224, 4266, 4308, 4352, 4395, 4439, 4483, 4528, + 4574, 4619, 4665, 4712, 4759, 4807, 4855, 4903, + 4953, 5002, 5052, 5103, 5154, 5205, 5257, 5310, + 5363, 5417, 5471, 5525, 5581, 5636, 5693, 5750, + 5807, 5865, 5924, 5983, 6043, 6104, 6165, 6226, + 6289, 6351, 6415, 6479, 6544, 6609, 6675, 6742, + 6810, 6878, 6946, 7016, 7086, 7157, 7229, 7301, + 7374, 7448, 7522, 7597, 7673, 7750, 7828, 7906, + 7985, 8065, 8145, 8227, 8309, 8392, 8476, 8561, + 8647, 8733, 8820, 8909, 8998, 9088, 9178, 9270, + 9363, 9457, 9551, 9647, 9743, 9841, 9939, 10038, + 10139, 10240, 10343, 10446, 10550, 10656, 10763, 10870, + 10979, 11089, 11200, 11312, 11425, 11539, 11654, 11771, + 11889, 12008, 12128, 12249, 12371, 12495, 12620, 12746, + 12874, 13002, 13132, 13264, 13396, 13530, 13666, 13802, + 13940, 14080, 14221, 14363, 14506, 14652, 14798, 14946, + 15096, 15247, 15399, 15553, 15709, 15866, 16024, 16184, + 16346, 16510, 16675, 16842, 17010, 17180, 17352, 17526, + 17701, 17878, 18057, 18237, 18420, 18604, 18790, 18978, + 19167, 19359, 19553, 19748, 19946, 20145, 20347, 20550, + 20756, 20963, 21173, 21385, 21598, 21814, 22033, 22253, + 22475, 22700, 22927, 23156, 23388, 23622, 23858, 24097, + 24338, 24581, 24827, 25075, 25326, 25579, 25835, 26093, + 26354, 26618, 26884, 27153, 27424, 27699, 27976, 28255, + 28538, 28823, 29112, 29403, 29697, 29994, 30294, 30597, + 30903, 31212, 31524, 31839, 32157, 32479, 32804 +}; + +static const int accel_st_tb[31] = { + 340, 351, 363, 375, 388, 401, 414, 428, + 443, 458, 473, 489, 506, 523, 541, 559, + 578, 597, 617, 638, 660, 682, 705, 729, + 753, 779, 805, 832, 860, 889, 919 +}; + +static const int gyro_6050_st_tb[31] = { + 3275, 3425, 3583, 3748, 3920, 4100, 4289, 4486, + 4693, 4909, 5134, 5371, 5618, 5876, 6146, 6429, + 6725, 7034, 7358, 7696, 8050, 8421, 8808, 9213, + 9637, 10080, 10544, 11029, 11537, 12067, 12622 +}; + +static const int gyro_3500_st_tb[255] = { + 2620, 2646, 2672, 2699, 2726, 2753, 2781, 2808, + 2837, 2865, 2894, 2923, 2952, 2981, 3011, 3041, + 3072, 3102, 3133, 3165, 3196, 3228, 3261, 3293, + 3326, 3359, 3393, 3427, 3461, 3496, 3531, 3566, + 3602, 3638, 3674, 3711, 3748, 3786, 3823, 3862, + 3900, 3939, 3979, 4019, 4059, 4099, 4140, 4182, + 4224, 4266, 4308, 4352, 4395, 4439, 4483, 4528, + 4574, 4619, 4665, 4712, 4759, 4807, 4855, 4903, + 4953, 5002, 5052, 5103, 5154, 5205, 5257, 5310, + 5363, 5417, 5471, 5525, 5581, 5636, 5693, 5750, + 5807, 5865, 5924, 5983, 6043, 6104, 6165, 6226, + 6289, 6351, 6415, 6479, 6544, 6609, 6675, 6742, + 6810, 6878, 6946, 7016, 7086, 7157, 7229, 7301, + 7374, 7448, 7522, 7597, 7673, 7750, 7828, 7906, + 7985, 8065, 8145, 8227, 8309, 8392, 8476, 8561, + 8647, 8733, 8820, 8909, 8998, 9088, 9178, 9270, + 9363, 9457, 9551, 9647, 9743, 9841, 9939, 10038, + 10139, 10240, 10343, 10446, 10550, 10656, 10763, 10870, + 10979, 11089, 11200, 11312, 11425, 11539, 11654, 11771, + 11889, 12008, 12128, 12249, 12371, 12495, 12620, 12746, + 12874, 13002, 13132, 13264, 13396, 13530, 13666, 13802, + 13940, 14080, 14221, 14363, 14506, 14652, 14798, 14946, + 15096, 15247, 15399, 15553, 15709, 15866, 16024, 16184, + 16346, 16510, 16675, 16842, 17010, 17180, 17352, 17526, + 17701, 17878, 18057, 18237, 18420, 18604, 18790, 18978, + 19167, 19359, 19553, 19748, 19946, 20145, 20347, 20550, + 20756, 20963, 21173, 21385, 21598, 21814, 22033, 22253, + 22475, 22700, 22927, 23156, 23388, 23622, 23858, 24097, + 24338, 24581, 24827, 25075, 25326, 25579, 25835, 26093, + 26354, 26618, 26884, 27153, 27424, 27699, 27976, 28255, + 28538, 28823, 29112, 29403, 29697, 29994, 30294, 30597, + 30903, 31212, 31524, 31839, 32157, 32479, 32804 +}; + +char *wr_pr_debug_begin(u8 const *data, u32 len, char *string) +{ + int ii; + string = kmalloc(len * 2 + 1, GFP_KERNEL); + for (ii = 0; ii < len; ii++) + sprintf(&string[ii * 2], "%02X", data[ii]); + string[len * 2] = 0; + return string; +} + +char *wr_pr_debug_end(char *string) +{ + kfree(string); + return ""; +} + +int mpu_memory_write(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 const *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf[513]; + + struct i2c_msg msgs[3]; + int res; + + if (!data || !st) + return -EINVAL; + + if (len >= (sizeof(buf) - 1)) + return -ENOMEM; + + bank[0] = REG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf[0] = REG_MEM_RW; + memcpy(buf + 1, data, len); + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = (u8 *)buf; + msgs[2].len = len + 1; + + INV_I2C_INC_MPUWRITE(3 + 3 + (2 + len)); +#if CONFIG_DYNAMIC_DEBUG + { + char *write = 0; + pr_debug("%s WM%02X%02X%02X%s%s - %d\n", st->hw->name, + mpu_addr, bank[1], addr[1], + wr_pr_debug_begin(data, len, write), + wr_pr_debug_end(write), + len); + } +#endif + + res = i2c_transfer(st->sl_handle, msgs, 3); + if (res != 3) { + if (res >= 0) + res = -EIO; + return res; + } else { + return 0; + } +} + +int mpu_memory_read(struct inv_mpu_state *st, u8 mpu_addr, u16 mem_addr, + u32 len, u8 *data) +{ + u8 bank[2]; + u8 addr[2]; + u8 buf; + + struct i2c_msg msgs[4]; + int res; + + if (!data || !st) + return -EINVAL; + + bank[0] = REG_BANK_SEL; + bank[1] = mem_addr >> 8; + + addr[0] = REG_MEM_START_ADDR; + addr[1] = mem_addr & 0xFF; + + buf = REG_MEM_RW; + + /* write message */ + msgs[0].addr = mpu_addr; + msgs[0].flags = 0; + msgs[0].buf = bank; + msgs[0].len = sizeof(bank); + + msgs[1].addr = mpu_addr; + msgs[1].flags = 0; + msgs[1].buf = addr; + msgs[1].len = sizeof(addr); + + msgs[2].addr = mpu_addr; + msgs[2].flags = 0; + msgs[2].buf = &buf; + msgs[2].len = 1; + + msgs[3].addr = mpu_addr; + msgs[3].flags = I2C_M_RD; + msgs[3].buf = data; + msgs[3].len = len; + + res = i2c_transfer(st->sl_handle, msgs, 4); + if (res != 4) { + if (res >= 0) + res = -EIO; + } else + res = 0; + + INV_I2C_INC_MPUWRITE(3 + 3 + 3); + INV_I2C_INC_MPUREAD(len); +#if CONFIG_DYNAMIC_DEBUG + { + char *read = 0; + pr_debug("%s RM%02X%02X%02X%02X - %s%s\n", st->hw->name, + mpu_addr, bank[1], addr[1], len, + wr_pr_debug_begin(data, len, read), + wr_pr_debug_end(read)); + } +#endif + + return res; +} + +int mpu_memory_write_unaligned(struct inv_mpu_state *st, u16 key, int len, + u8 const *d) +{ + u32 addr; + int start, end; + int len1, len2; + int result = 0; + + if (len > MPU_MEM_BANK_SIZE) + return -EINVAL; + addr = inv_dmp_get_address(key); + if (addr > MPU6XXX_MAX_MPU_MEM) + return -EINVAL; + + start = (addr >> 8); + end = ((addr + len - 1) >> 8); + if (start == end) { + result = mpu_memory_write(st, st->i2c_addr, addr, len, d); + } else { + end <<= 8; + len1 = end - addr; + len2 = len - len1; + result = mpu_memory_write(st, st->i2c_addr, addr, len1, d); + result |= mpu_memory_write(st, st->i2c_addr, end, len2, + d + len1); + } + + return result; +} + +/** + * index_of_key()- Inverse lookup of the index of an MPL product key . + * @key: the MPL product indentifier also referred to as 'key'. + */ +static short index_of_key(u16 key) +{ + int i; + for (i = 0; i < NUM_OF_PROD_REVS; i++) + if (prod_rev_map[i].mpl_product_key == key) + return (short)i; + return -EINVAL; +} + +int inv_get_silicon_rev_mpu6500(struct inv_mpu_state *st) +{ + struct inv_chip_info_s *chip_info = &st->chip_info; + int result; + u8 whoami, sw_rev; + + result = inv_i2c_read(st, REG_WHOAMI, 1, &whoami); + if (result) + return result; + if (whoami != MPU6500_ID && whoami != MPU9250_ID && + whoami != MPU9350_ID && whoami != MPU6515_ID) + return -EINVAL; + + /*memory read need more time after power up */ + msleep(POWER_UP_TIME); + result = mpu_memory_read(st, st->i2c_addr, + MPU6500_MEM_REV_ADDR, 1, &sw_rev); + sw_rev &= INV_MPU_REV_MASK; + if (result) + return result; + if (sw_rev != 0) + return -EINVAL; + /* these values are place holders and not real values */ + chip_info->product_id = MPU6500_PRODUCT_REVISION; + chip_info->product_revision = MPU6500_PRODUCT_REVISION; + chip_info->silicon_revision = MPU6500_PRODUCT_REVISION; + chip_info->software_revision = sw_rev; + chip_info->gyro_sens_trim = DEFAULT_GYRO_TRIM; + chip_info->accel_sens_trim = DEFAULT_ACCEL_TRIM; + chip_info->multi = 1; + + return 0; +} + +int inv_get_silicon_rev_mpu6050(struct inv_mpu_state *st) +{ + int result; + struct inv_reg_map_s *reg; + u8 prod_ver = 0x00, prod_rev = 0x00; + struct prod_rev_map_t *p_rev; + u8 bank = + (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0); + u16 mem_addr = ((bank << 8) | MEM_ADDR_PROD_REV); + u16 key; + u8 regs[5]; + u16 sw_rev; + short index; + struct inv_chip_info_s *chip_info = &st->chip_info; + reg = &st->reg; + + result = inv_i2c_read(st, REG_PRODUCT_ID, 1, &prod_ver); + if (result) + return result; + prod_ver &= 0xf; + /*memory read need more time after power up */ + msleep(POWER_UP_TIME); + result = mpu_memory_read(st, st->i2c_addr, mem_addr, 1, &prod_rev); + if (result) + return result; + prod_rev >>= 2; + /* clean the prefetch and cfg user bank bits */ + result = inv_i2c_single_write(st, reg->bank_sel, 0); + if (result) + return result; + /* get the software-product version, read from XA_OFFS_L */ + result = inv_i2c_read(st, REG_XA_OFFS_L_TC, + SOFT_PROD_VER_BYTES, regs); + if (result) + return result; + + sw_rev = (regs[4] & 0x01) << 2 | /* 0x0b, bit 0 */ + (regs[2] & 0x01) << 1 | /* 0x09, bit 0 */ + (regs[0] & 0x01); /* 0x07, bit 0 */ + /* if 0, use the product key to determine the type of part */ + if (sw_rev == 0) { + key = MPL_PROD_KEY(prod_ver, prod_rev); + if (key == 0) + return -EINVAL; + index = index_of_key(key); + if (index < 0 || index >= NUM_OF_PROD_REVS) + return -EINVAL; + /* check MPL is compiled for this device */ + if (prod_rev_map[index].silicon_rev != MPU_SILICON_REV_B1) + return -EINVAL; + p_rev = (struct prod_rev_map_t *)&prod_rev_map[index]; + /* if valid, use the software product key */ + } else if (sw_rev < ARRAY_SIZE(sw_rev_map)) { + p_rev = (struct prod_rev_map_t *)&sw_rev_map[sw_rev]; + } else { + return -EINVAL; + } + chip_info->product_id = prod_ver; + chip_info->product_revision = prod_rev; + chip_info->silicon_revision = p_rev->silicon_rev; + chip_info->software_revision = sw_rev; + chip_info->gyro_sens_trim = p_rev->gyro_trim; + chip_info->accel_sens_trim = p_rev->accel_trim; + if (chip_info->accel_sens_trim == 0) + chip_info->accel_sens_trim = DEFAULT_ACCEL_TRIM; + chip_info->multi = DEFAULT_ACCEL_TRIM / chip_info->accel_sens_trim; + if (chip_info->multi != 1) + pr_info("multi is %d\n", chip_info->multi); + return result; +} + +/** + * read_accel_hw_self_test_prod_shift()- read the accelerometer hardware + * self-test bias shift calculated + * during final production test and + * stored in chip non-volatile memory. + * @st: main data structure. + * @st_prod: A pointer to an array of 3 elements to hold the values + * for production hardware self-test bias shifts returned to the + * user. + * @accel_sens: accel sensitivity. + */ +static int read_accel_hw_self_test_prod_shift(struct inv_mpu_state *st, + int *st_prod, int *accel_sens) +{ + u8 regs[4]; + u8 shift_code[3]; + int result, i; + + for (i = 0; i < 3; i++) + st_prod[i] = 0; + + result = inv_i2c_read(st, REG_ST_GCT_X, ARRAY_SIZE(regs), regs); + if (result) + return result; + if ((0 == regs[0]) && (0 == regs[1]) && + (0 == regs[2]) && (0 == regs[3])) + return -EINVAL; + shift_code[X] = ((regs[0] & 0xE0) >> 3) | ((regs[3] & 0x30) >> 4); + shift_code[Y] = ((regs[1] & 0xE0) >> 3) | ((regs[3] & 0x0C) >> 2); + shift_code[Z] = ((regs[2] & 0xE0) >> 3) | (regs[3] & 0x03); + for (i = 0; i < 3; i++) + if (shift_code[i] != 0) + st_prod[i] = accel_sens[i] * + accel_st_tb[shift_code[i] - 1]; + + return 0; +} + +/** +* inv_check_accel_self_test()- check accel self test. this function returns +* zero as success. A non-zero return value +* indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_accel_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg){ + int gravity, reg_z_avg, g_z_sign, j, ret_val; + int tmp; + int st_shift_prod[THREE_AXIS], st_shift_cust[THREE_AXIS]; + int st_shift_ratio[THREE_AXIS]; + int accel_sens[THREE_AXIS]; + + if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV && + st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV) + return 0; + g_z_sign = 1; + ret_val = 0; + tmp = DEF_ST_SCALE * DEF_ST_PRECISION / DEF_ST_ACCEL_FS_MG; + for (j = 0; j < 3; j++) + accel_sens[j] = tmp; + + if (MPL_PROD_KEY(st->chip_info.product_id, + st->chip_info.product_revision) == + MPU_PRODUCT_KEY_B1_E1_5) { + /* half sensitivity Z accelerometer parts */ + accel_sens[Z] /= 2; + } else { + /* half sensitivity X, Y, Z accelerometer parts */ + accel_sens[X] /= st->chip_info.multi; + accel_sens[Y] /= st->chip_info.multi; + accel_sens[Z] /= st->chip_info.multi; + } + gravity = accel_sens[Z]; + reg_z_avg = reg_avg[Z] - g_z_sign * gravity * DEF_ST_PRECISION; + ret_val = read_accel_hw_self_test_prod_shift(st, st_shift_prod, + accel_sens); + if (ret_val) + return ret_val; + + for (j = 0; j < 3; j++) { + st_shift_cust[j] = abs(reg_avg[j] - st_avg[j]); + if (st_shift_prod[j]) { + tmp = st_shift_prod[j] / DEF_ST_PRECISION; + st_shift_ratio[j] = abs(st_shift_cust[j] / tmp + - DEF_ST_PRECISION); + if (st_shift_ratio[j] > DEF_ACCEL_ST_SHIFT_DELTA) + ret_val = 1; + } else { + if (st_shift_cust[j] < + DEF_ACCEL_ST_SHIFT_MIN * gravity) + ret_val = 1; + if (st_shift_cust[j] > + DEF_ACCEL_ST_SHIFT_MAX * gravity) + ret_val = 1; + } + } + + return ret_val; +} + +/** +* inv_check_3500_gyro_self_test() check gyro self test. this function returns +* zero as success. A non-zero return value +* indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ + +static int inv_check_3500_gyro_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg){ + int result; + int gst[3], ret_val; + int gst_otp[3], i; + u8 st_code[THREE_AXIS]; + ret_val = 0; + + for (i = 0; i < 3; i++) + gst[i] = st_avg[i] - reg_avg[i]; + result = inv_i2c_read(st, REG_3500_OTP, THREE_AXIS, st_code); + if (result) + return result; + gst_otp[0] = 0; + gst_otp[1] = 0; + gst_otp[2] = 0; + for (i = 0; i < 3; i++) { + if (st_code[i] != 0) + gst_otp[i] = gyro_3500_st_tb[st_code[i] - 1]; + } + /* check self test value passing criterion. Using the DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) { + if (gst_otp[i] == 0) { + if (abs(gst[i]) * DEF_ST_TOR < DEF_ST_OTP0_THRESH * + DEF_ST_PRECISION * + DEF_GYRO_SCALE) + ret_val |= (1 << i); + } else { + if (abs(gst[i]/gst_otp[i] - DEF_ST_PRECISION) > + DEF_GYRO_CT_SHIFT_DELTA) + ret_val |= (1 << i); + } + } + /* check for absolute value passing criterion. Using DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) { + if (abs(reg_avg[i]) > DEF_ST_TOR * DEF_ST_ABS_THRESH * + DEF_ST_PRECISION * DEF_GYRO_SCALE) + ret_val |= (1 << i); + } + + return ret_val; +} + +/** +* inv_check_6050_gyro_self_test() - check 6050 gyro self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_6050_gyro_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg){ + int result; + int ret_val; + int st_shift_prod[3], st_shift_cust[3], st_shift_ratio[3], i; + u8 regs[3]; + + if (st->chip_info.software_revision < DEF_OLDEST_SUPP_SW_REV && + st->chip_info.product_revision < DEF_OLDEST_SUPP_PROD_REV) + return 0; + + ret_val = 0; + result = inv_i2c_read(st, REG_ST_GCT_X, 3, regs); + if (result) + return result; + regs[X] &= 0x1f; + regs[Y] &= 0x1f; + regs[Z] &= 0x1f; + for (i = 0; i < 3; i++) { + if (regs[i] != 0) + st_shift_prod[i] = gyro_6050_st_tb[regs[i] - 1]; + else + st_shift_prod[i] = 0; + } + st_shift_prod[1] = -st_shift_prod[1]; + + for (i = 0; i < 3; i++) { + st_shift_cust[i] = st_avg[i] - reg_avg[i]; + if (st_shift_prod[i]) { + st_shift_ratio[i] = abs(st_shift_cust[i] / + st_shift_prod[i] - DEF_ST_PRECISION); + if (st_shift_ratio[i] > DEF_GYRO_CT_SHIFT_DELTA) + ret_val = 1; + } else { + if (st_shift_cust[i] < DEF_ST_PRECISION * + DEF_GYRO_CT_SHIFT_MIN * DEF_SELFTEST_GYRO_SENS) + ret_val = 1; + if (st_shift_cust[i] > DEF_ST_PRECISION * + DEF_GYRO_CT_SHIFT_MAX * DEF_SELFTEST_GYRO_SENS) + ret_val = 1; + } + } + /* check for absolute value passing criterion. Using DEF_ST_TOR + * for certain degree of tolerance */ + for (i = 0; i < 3; i++) + if (abs(reg_avg[i]) > DEF_ST_TOR * DEF_ST_ABS_THRESH * + DEF_ST_PRECISION * DEF_GYRO_SCALE) + ret_val = 1; + + return ret_val; +} + +/** +* inv_check_6500_gyro_self_test() - check 6500 gyro self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_6500_gyro_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg) { + u8 regs[3]; + int ret_val, result; + int otp_value_zero = 0; + int st_shift_prod[3], st_shift_cust[3], i; + + ret_val = 0; + result = inv_i2c_read(st, REG_6500_XG_ST_DATA, 3, regs); + if (result) + return result; + pr_debug("%s self_test gyro shift_code - %02x %02x %02x\n", + st->hw->name, regs[0], regs[1], regs[2]); + + for (i = 0; i < 3; i++) { + if (regs[i] != 0) { + st_shift_prod[i] = mpu_6500_st_tb[regs[i] - 1]; + } else { + st_shift_prod[i] = 0; + otp_value_zero = 1; + } + } + pr_debug("%s self_test gyro st_shift_prod - %+d %+d %+d\n", + st->hw->name, st_shift_prod[0], st_shift_prod[1], + st_shift_prod[2]); + + for (i = 0; i < 3; i++) { + st_shift_cust[i] = st_avg[i] - reg_avg[i]; + if (!otp_value_zero) { + /* Self Test Pass/Fail Criteria A */ + if (st_shift_cust[i] < DEF_6500_GYRO_CT_SHIFT_DELTA + * st_shift_prod[i]) + ret_val = 1; + } else { + /* Self Test Pass/Fail Criteria B */ + if (st_shift_cust[i] < DEF_GYRO_ST_AL * + DEF_SELFTEST_GYRO_SENS * + DEF_ST_PRECISION) + ret_val = 1; + } + } + pr_debug("%s self_test gyro st_shift_cust - %+d %+d %+d\n", + st->hw->name, st_shift_cust[0], st_shift_cust[1], + st_shift_cust[2]); + + if (ret_val == 0) { + /* Self Test Pass/Fail Criteria C */ + for (i = 0; i < 3; i++) + if (abs(reg_avg[i]) > DEF_GYRO_OFFSET_MAX * + DEF_SELFTEST_GYRO_SENS * + DEF_ST_PRECISION) + ret_val = 1; + } + + return ret_val; +} + +/** +* inv_check_6500_accel_self_test() - check 6500 accel self test. this function +* returns zero as success. A non-zero return +* value indicates failure in self test. +* @*st: main data structure. +* @*reg_avg: average value of normal test. +* @*st_avg: average value of self test +*/ +static int inv_check_6500_accel_self_test(struct inv_mpu_state *st, + int *reg_avg, int *st_avg) { + int ret_val, result; + int st_shift_prod[3], st_shift_cust[3], st_shift_ratio[3], i; + u8 regs[3]; + int otp_value_zero = 0; + +#define ACCEL_ST_AL_MIN ((DEF_ACCEL_ST_AL_MIN * DEF_ST_SCALE \ + / DEF_ST_6500_ACCEL_FS_MG) * DEF_ST_PRECISION) +#define ACCEL_ST_AL_MAX ((DEF_ACCEL_ST_AL_MAX * DEF_ST_SCALE \ + / DEF_ST_6500_ACCEL_FS_MG) * DEF_ST_PRECISION) + + ret_val = 0; + result = inv_i2c_read(st, REG_6500_XA_ST_DATA, 3, regs); + if (result) + return result; + pr_debug("%s self_test accel shift_code - %02x %02x %02x\n", + st->hw->name, regs[0], regs[1], regs[2]); + + for (i = 0; i < 3; i++) { + if (regs[i] != 0) { + st_shift_prod[i] = mpu_6500_st_tb[regs[i] - 1]; + } else { + st_shift_prod[i] = 0; + otp_value_zero = 1; + } + } + pr_debug("%s self_test accel st_shift_prod - %+d %+d %+d\n", + st->hw->name, st_shift_prod[0], st_shift_prod[1], + st_shift_prod[2]); + + if (!otp_value_zero) { + /* Self Test Pass/Fail Criteria A */ + for (i = 0; i < 3; i++) { + st_shift_cust[i] = st_avg[i] - reg_avg[i]; + st_shift_ratio[i] = abs(st_shift_cust[i] / + st_shift_prod[i] - DEF_ST_PRECISION); + if (st_shift_ratio[i] > DEF_6500_ACCEL_ST_SHIFT_DELTA) + ret_val = 1; + } + } else { + /* Self Test Pass/Fail Criteria B */ + for (i = 0; i < 3; i++) { + st_shift_cust[i] = abs(st_avg[i] - reg_avg[i]); + if (st_shift_cust[i] < ACCEL_ST_AL_MIN || + st_shift_cust[i] > ACCEL_ST_AL_MAX) + ret_val = 1; + } + } + pr_debug("%s self_test accel st_shift_cust - %+d %+d %+d\n", + st->hw->name, st_shift_cust[0], st_shift_cust[1], + st_shift_cust[2]); + + return ret_val; +} + +/* + * inv_do_test() - do the actual test of self testing + */ +int inv_do_test(struct inv_mpu_state *st, int self_test_flag, + int *gyro_result, int *accel_result) +{ + struct inv_reg_map_s *reg; + int result, i, j, packet_size; + u8 data[BYTES_PER_SENSOR * 2], d; + bool has_accel; + int fifo_count, packet_count, ind, s; + + reg = &st->reg; + has_accel = (st->chip_type != INV_ITG3500); + if (has_accel) + packet_size = BYTES_PER_SENSOR * 2; + else + packet_size = BYTES_PER_SENSOR; + + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) + return result; + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + /* disable fifo reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + if (result) + return result; + /* clear FIFO */ + result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_RST); + if (result) + return result; + /* setup parameters */ + result = inv_i2c_single_write(st, reg->lpf, INV_FILTER_98HZ); + if (result) + return result; + + if (INV_MPU6500 == st->chip_type) { + /* config accel LPF register for MPU6500 */ + result = inv_i2c_single_write(st, REG_6500_ACCEL_CONFIG2, + DEF_ST_MPU6500_ACCEL_LPF | + BIT_FIFO_SIZE_1K); + if (result) + return result; + } + + result = inv_i2c_single_write(st, reg->sample_rate_div, + DEF_SELFTEST_SAMPLE_RATE); + if (result) + return result; + /* wait for the sampling rate change to stabilize */ + mdelay(INV_MPU_SAMPLE_RATE_CHANGE_STABLE); + result = inv_i2c_single_write(st, reg->gyro_config, + self_test_flag | DEF_SELFTEST_GYRO_FS); + if (result) + return result; + if (has_accel) { + if (INV_MPU6500 == st->chip_type) + d = DEF_SELFTEST_6500_ACCEL_FS; + else + d = DEF_SELFTEST_ACCEL_FS; + d |= self_test_flag; + result = inv_i2c_single_write(st, reg->accel_config, d); + if (result) + return result; + } + /* wait for the output to get stable */ + if (self_test_flag) { + if (INV_MPU6500 == st->chip_type) + msleep(DEF_ST_6500_STABLE_TIME); + else + msleep(DEF_ST_STABLE_TIME); + } + + /* enable FIFO reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, BIT_FIFO_EN); + if (result) + return result; + /* enable sensor output to FIFO */ + if (has_accel) + d = BITS_GYRO_OUT | BIT_ACCEL_OUT; + else + d = BITS_GYRO_OUT; + for (i = 0; i < THREE_AXIS; i++) { + gyro_result[i] = 0; + accel_result[i] = 0; + } + s = 0; + while (s < st->self_test.samples) { + result = inv_i2c_single_write(st, reg->fifo_en, d); + if (result) + return result; + mdelay(DEF_GYRO_WAIT_TIME); + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + return result; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + pr_debug("%s self_test fifo_count - %d\n", + st->hw->name, fifo_count); + packet_count = fifo_count / packet_size; + i = 0; + while ((i < packet_count) && (s < st->self_test.samples)) { + short vals[3]; + result = inv_i2c_read(st, reg->fifo_r_w, + packet_size, data); + if (result) + return result; + ind = 0; + if (has_accel) { + for (j = 0; j < THREE_AXIS; j++) { + vals[j] = (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + accel_result[j] += vals[j]; + } + ind += BYTES_PER_SENSOR; + pr_debug( + "%s self_test accel data - %d %+d %+d %+d", + st->hw->name, s, vals[0], vals[1], vals[2]); + } + + for (j = 0; j < THREE_AXIS; j++) { + vals[j] = (short)be16_to_cpup( + (__be16 *)(&data[ind + 2 * j])); + gyro_result[j] += vals[j]; + } + pr_debug("%s self_test gyro data - %d %+d %+d %+d", + st->hw->name, s, vals[0], vals[1], vals[2]); + + s++; + i++; + } + } + + if (has_accel) { + for (j = 0; j < THREE_AXIS; j++) { + accel_result[j] = accel_result[j] / s; + accel_result[j] *= DEF_ST_PRECISION; + } + } + for (j = 0; j < THREE_AXIS; j++) { + gyro_result[j] = gyro_result[j] / s; + gyro_result[j] *= DEF_ST_PRECISION; + } + + return 0; +} + +/* + * inv_recover_setting() recover the old settings after everything is done + */ +void inv_recover_setting(struct inv_mpu_state *st) +{ + struct inv_reg_map_s *reg; + int data; + + reg = &st->reg; + inv_i2c_single_write(st, reg->gyro_config, + st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT); + inv_i2c_single_write(st, reg->lpf, st->chip_config.lpf); + data = ONE_K_HZ/st->chip_config.new_fifo_rate - 1; + inv_i2c_single_write(st, reg->sample_rate_div, data); + /* wait for the sampling rate change to stabilize */ + mdelay(INV_MPU_SAMPLE_RATE_CHANGE_STABLE); + if (INV_ITG3500 != st->chip_type) { + inv_i2c_single_write(st, reg->accel_config, + (st->chip_config.accel_fs << + ACCEL_CONFIG_FSR_SHIFT)); + } + inv_reset_offset_reg(st, false); + st->switch_gyro_engine(st, false); + st->switch_accel_engine(st, false); + st->set_power_state(st, false); +} + + +int inv_power_up_self_test(struct inv_mpu_state *st) +{ + int result; + + result = st->set_power_state(st, true); + if (result) + return result; + result = st->switch_accel_engine(st, true); + if (result) + return result; + result = st->switch_gyro_engine(st, true); + if (result) + return result; + + return 0; +} + +/* + * inv_hw_self_test() - main function to do hardware self test + */ +int inv_hw_self_test(struct inv_mpu_state *st) +{ + int result; + int gyro_bias_st[THREE_AXIS], gyro_bias_regular[THREE_AXIS]; + int accel_bias_st[THREE_AXIS], accel_bias_regular[THREE_AXIS]; + int test_times, i; + char compass_result, accel_result, gyro_result; + + result = inv_power_up_self_test(st); + if (result) + return result; + result = inv_reset_offset_reg(st, true); + if (result) + return result; + compass_result = 0; + accel_result = 0; + gyro_result = 0; + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, 0, gyro_bias_regular, + accel_bias_regular); + if (result == -EAGAIN) + test_times--; + else + test_times = 0; + } + if (result) + goto test_fail; + pr_debug("%s self_test accel bias_regular - %+d %+d %+d\n", + st->hw->name, accel_bias_regular[0], + accel_bias_regular[1], accel_bias_regular[2]); + pr_debug("%s self_test gyro bias_regular - %+d %+d %+d\n", + st->hw->name, gyro_bias_regular[0], gyro_bias_regular[1], + gyro_bias_regular[2]); + + for (i = 0; i < 3; i++) { + st->gyro_bias[i] = gyro_bias_regular[i]; + st->accel_bias[i] = accel_bias_regular[i]; + } + + test_times = DEF_ST_TRY_TIMES; + while (test_times > 0) { + result = inv_do_test(st, BITS_SELF_TEST_EN, gyro_bias_st, + accel_bias_st); + if (result == -EAGAIN) + test_times--; + else + break; + } + if (result) + goto test_fail; + pr_debug("%s self_test accel bias_st - %+d %+d %+d\n", + st->hw->name, accel_bias_st[0], accel_bias_st[1], + accel_bias_st[2]); + pr_debug("%s self_test gyro bias_st - %+d %+d %+d\n", + st->hw->name, gyro_bias_st[0], gyro_bias_st[1], + gyro_bias_st[2]); + + if (st->chip_type == INV_ITG3500) { + gyro_result = !inv_check_3500_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + } else { + if (st->chip_config.has_compass) + compass_result = !st->slave_compass->self_test(st); + + if (INV_MPU6050 == st->chip_type) { + accel_result = !inv_check_accel_self_test(st, + accel_bias_regular, accel_bias_st); + gyro_result = !inv_check_6050_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + } else if (INV_MPU6500 == st->chip_type) { + accel_result = !inv_check_6500_accel_self_test(st, + accel_bias_regular, accel_bias_st); + gyro_result = !inv_check_6500_gyro_self_test(st, + gyro_bias_regular, gyro_bias_st); + } + } + +test_fail: + inv_recover_setting(st); + + return (compass_result << DEF_ST_COMPASS_RESULT_SHIFT) | + (accel_result << DEF_ST_ACCEL_RESULT_SHIFT) | gyro_result; +} + +static int inv_load_firmware(struct inv_mpu_state *st, + u8 *data, int size) +{ + int bank, write_size; + int result; + u16 memaddr; + + /* first bank start at MPU_DMP_LOAD_START */ + write_size = MPU_MEM_BANK_SIZE - MPU_DMP_LOAD_START; + memaddr = ((0 << 8) | MPU_DMP_LOAD_START); + data += MPU_DMP_LOAD_START; + result = mem_w(memaddr, write_size, data); + if (result) + return result; + size -= MPU_MEM_BANK_SIZE; + data += write_size; + + /* Write and verify memory */ + for (bank = 1; size > 0; bank++, size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = ((bank << 8) | 0x00); + + result = mem_w(memaddr, write_size, data); + if (result) + return result; + } + return 0; +} + +static int inv_verify_firmware(struct inv_mpu_state *st, + u8 *data, int size) +{ + int bank, write_size; + int result; + u16 memaddr; + u8 firmware[MPU_MEM_BANK_SIZE]; + + /* Write and verify memory */ + size -= MPU_MEM_BANK_SIZE; + data += MPU_MEM_BANK_SIZE; + for (bank = 1; size > 0; bank++, + size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = ((bank << 8) | 0x00); + result = mpu_memory_read(st, + st->i2c_addr, memaddr, write_size, firmware); + if (result) + return result; + if (0 != memcmp(firmware, data, write_size)) + return -EINVAL; + } + return 0; +} + +int inv_enable_pedometer_interrupt(struct inv_mpu_state *st, bool en) +{ + u8 reg[3]; + + if (en) { + reg[0] = 0xf4; + reg[1] = 0x44; + reg[2] = 0xf1; + + } else { + reg[0] = 0xf1; + reg[1] = 0xf1; + reg[2] = 0xf1; + } + + return mem_w_key(KEY_CFG_PED_INT, ARRAY_SIZE(reg), reg); +} + +int inv_enable_pedometer(struct inv_mpu_state *st, bool en) +{ + u8 d[1]; + + if (en) + d[0] = 0xf1; + else + d[0] = 0xff; + + return mem_w_key(KEY_CFG_PED_ENABLE, ARRAY_SIZE(d), d); +} + +int inv_get_pedometer_steps(struct inv_mpu_state *st, u32 *steps) +{ + u8 d[4]; + int result; + + result = mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_D_PEDSTD_STEPCTR), 4, d); + *steps = (u32)be32_to_cpup((__be32 *)(d)); + + return result; +} + +int inv_get_pedometer_time(struct inv_mpu_state *st, u32 *time) +{ + u8 d[4]; + int result; + + result = mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_D_PEDSTD_TIMECTR), 4, d); + *time = (u32)be32_to_cpup((__be32 *)(d)); + + return result; +} + +int inv_set_display_orient_interrupt_dmp(struct inv_mpu_state *st, bool on) +{ + int r; + u8 rn[] = {0xf4, 0x41}; + u8 rf[] = {0xd8, 0xd8}; + + if (on) + r = mem_w_key(KEY_CFG_DISPLAY_ORIENT_INT, ARRAY_SIZE(rn), rn); + else + r = mem_w_key(KEY_CFG_DISPLAY_ORIENT_INT, ARRAY_SIZE(rf), rf); + + return r; +} + +static int inv_set_tap_interrupt_dmp(struct inv_mpu_state *st, u8 on) +{ + int result; + u16 d; + + if (on) + d = 192; + else + d = 128; + + result = inv_write_2bytes(st, KEY_DMP_TAP_GATE, d); + + return result; +} + +/* + * inv_set_tap_threshold_dmp(): + * Sets the tap threshold in the dmp + * Simultaneously sets secondary tap threshold to help correct the tap + * direction for soft taps. + */ +int inv_set_tap_threshold_dmp(struct inv_mpu_state *st, u16 threshold) +{ + int result; + int sampleDivider; + int scaledThreshold; + u32 dmpThreshold; + u8 sample_div; + const u32 accel_sens = (0x20000000 / 0x00010000); + + if (threshold > (1 << 15)) + return -EINVAL; + sample_div = st->sample_divider; + + sampleDivider = (1 + sample_div); + /* Scale factor corresponds linearly using + * 0 : 0 + * 25 : 0.0250 g/ms + * 50 : 0.0500 g/ms + * 100: 1.0000 g/ms + * 200: 2.0000 g/ms + * 400: 4.0000 g/ms + * 800: 8.0000 g/ms + */ + /*multiply by 1000 to avoid floating point 1000/1000*/ + scaledThreshold = threshold; + /* Convert to per sample */ + scaledThreshold *= sampleDivider; + + /* Scale to DMP 16 bit value */ + if (accel_sens != 0) + dmpThreshold = (u32)(scaledThreshold * accel_sens); + else + return -EINVAL; + dmpThreshold = dmpThreshold / DMP_PRECISION; + result = inv_write_2bytes(st, KEY_DMP_TAP_THR_Z, dmpThreshold); + if (result) + return result; + result = inv_write_2bytes(st, KEY_DMP_TAP_PREV_JERK_Z, + dmpThreshold * 3 / 4); + + return result; +} + + +/* + * inv_set_min_taps_dmp(): + * Indicates the minimum number of consecutive taps required + * before the DMP will generate an interrupt. + */ +int inv_set_min_taps_dmp(struct inv_mpu_state *st, u16 min_taps) +{ + u8 result; + + /* check if any spurious bit other the ones expected are set */ + if ((min_taps > DMP_MAX_MIN_TAPS) || (min_taps < 1)) + return -EINVAL; + + /* DMP tap count is zero-based. So single-tap is 0. + Furthermore, DMP code checks for tap_count > min_taps. + So we have to do minus 2 here. + For example, if the user expects any single tap will generate an + interrupt, (s)he will call inv_set_min_taps_dmp(1). + When DMP gets a single tap, tap_count = 0. To get + tap_count > min_taps, we have to decrement min_taps by 2 to -1. */ + result = inv_write_2bytes(st, KEY_DMP_TAP_MIN_TAPS, (u16)(min_taps-2)); + + return result; +} + +/* + * inv_set_tap_time_dmp(): + * Determines how long after a tap the DMP requires before + * another tap can be registered. + */ +int inv_set_tap_time_dmp(struct inv_mpu_state *st, u16 time) +{ + int result; + u16 dmpTime; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + result = inv_write_2bytes(st, KEY_DMP_TAPW_MIN, dmpTime); + + return result; +} + +/* + * inv_set_multiple_tap_time_dmp(): + * Determines how close together consecutive taps must occur + * to be considered double/triple taps. + */ +static int inv_set_multiple_tap_time_dmp(struct inv_mpu_state *st, u32 time) +{ + int result; + u16 dmpTime; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + result = inv_write_2bytes(st, KEY_DMP_TAP_NEXT_TAP_THRES, dmpTime); + + return result; +} + +int inv_q30_mult(int a, int b) +{ + u64 temp; + int result; + + temp = (u64)a * b; + result = (int)(temp >> DMP_MULTI_SHIFT); + + return result; +} + +static u16 inv_row_2_scale(const s8 *row) +{ + u16 b; + + if (row[0] > 0) + b = 0; + else if (row[0] < 0) + b = 4; + else if (row[1] > 0) + b = 1; + else if (row[1] < 0) + b = 5; + else if (row[2] > 0) + b = 2; + else if (row[2] < 0) + b = 6; + else + b = 7; + + return b; +} + +/** Converts an orientation matrix made up of 0,+1,and -1 to a scalar +* representation. +* @param[in] mtx Orientation matrix to convert to a scalar. +* @return Description of orientation matrix. The lowest 2 bits (0 and 1) +* represent the column the one is on for the +* first row, with the bit number 2 being the sign. The next 2 bits +* (3 and 4) represent +* the column the one is on for the second row with bit number 5 being +* the sign. +* The next 2 bits (6 and 7) represent the column the one is on for the +* third row with +* bit number 8 being the sign. In binary the identity matrix would therefor +* be: 010_001_000 or 0x88 in hex. +*/ +static u16 inv_orientation_matrix_to_scaler(const signed char *mtx) +{ + + u16 scalar; + scalar = inv_row_2_scale(mtx); + scalar |= inv_row_2_scale(mtx + 3) << 3; + scalar |= inv_row_2_scale(mtx + 6) << 6; + + return scalar; +} + +static int inv_gyro_dmp_cal(struct inv_mpu_state *st) +{ + int inv_gyro_orient; + u8 regs[3]; + int result; + + u8 tmpD = DINA4C; + u8 tmpE = DINACD; + u8 tmpF = DINA6C; + + inv_gyro_orient = + inv_orientation_matrix_to_scaler(st->plat_data.orientation); + + if ((inv_gyro_orient & 3) == 0) + regs[0] = tmpD; + else if ((inv_gyro_orient & 3) == 1) + regs[0] = tmpE; + else if ((inv_gyro_orient & 3) == 2) + regs[0] = tmpF; + if ((inv_gyro_orient & 0x18) == 0) + regs[1] = tmpD; + else if ((inv_gyro_orient & 0x18) == 0x8) + regs[1] = tmpE; + else if ((inv_gyro_orient & 0x18) == 0x10) + regs[1] = tmpF; + if ((inv_gyro_orient & 0xc0) == 0) + regs[2] = tmpD; + else if ((inv_gyro_orient & 0xc0) == 0x40) + regs[2] = tmpE; + else if ((inv_gyro_orient & 0xc0) == 0x80) + regs[2] = tmpF; + + result = mem_w_key(KEY_FCFG_1, ARRAY_SIZE(regs), regs); + if (result) + return result; + + if (inv_gyro_orient & 4) + regs[0] = DINA36 | 1; + else + regs[0] = DINA36; + if (inv_gyro_orient & 0x20) + regs[1] = DINA56 | 1; + else + regs[1] = DINA56; + if (inv_gyro_orient & 0x100) + regs[2] = DINA76 | 1; + else + regs[2] = DINA76; + result = mem_w_key(KEY_FCFG_3, ARRAY_SIZE(regs), regs); + + return result; +} + +static int inv_accel_dmp_cal(struct inv_mpu_state *st) +{ + int inv_accel_orient; + int result; + u8 regs[3]; + const u8 tmp[3] = { DINA0C, DINAC9, DINA2C }; + inv_accel_orient = + inv_orientation_matrix_to_scaler(st->plat_data.orientation); + + regs[0] = tmp[inv_accel_orient & 3]; + regs[1] = tmp[(inv_accel_orient >> 3) & 3]; + regs[2] = tmp[(inv_accel_orient >> 6) & 3]; + result = mem_w_key(KEY_FCFG_2, ARRAY_SIZE(regs), regs); + if (result) + return result; + + regs[0] = DINA26; + regs[1] = DINA46; + regs[2] = DINA66; + if (inv_accel_orient & 4) + regs[0] |= 1; + if (inv_accel_orient & 0x20) + regs[1] |= 1; + if (inv_accel_orient & 0x100) + regs[2] |= 1; + result = mem_w_key(KEY_FCFG_7, ARRAY_SIZE(regs), regs); + + return result; +} + +int inv_set_accel_bias_dmp(struct inv_mpu_state *st) +{ + int inv_accel_orient, result, i, accel_bias_body[3], out[3]; + int tmp[] = {1, 1, 1}; + int mask[] = {4, 0x20, 0x100}; + int accel_sf = 0x20000000;/* 536870912 */ + u8 *regs; + + inv_accel_orient = + inv_orientation_matrix_to_scaler(st->plat_data.orientation); + + for (i = 0; i < 3; i++) + if (inv_accel_orient & mask[i]) + tmp[i] = -1; + + for (i = 0; i < 3; i++) + accel_bias_body[i] = + st->input_accel_dmp_bias[(inv_accel_orient >> + (i * 3)) & 3] * tmp[i]; + for (i = 0; i < 3; i++) + accel_bias_body[i] = inv_q30_mult(accel_sf, + accel_bias_body[i]); + for (i = 0; i < 3; i++) + out[i] = cpu_to_be32p(&accel_bias_body[i]); + regs = (u8 *)out; + result = mem_w_key(KEY_D_ACCEL_BIAS, sizeof(out), regs); + + return result; +} + +/* + * inv_set_gyro_sf_dmp(): + * The gyro threshold, in dps, above which taps will be rejected. + */ +static int inv_set_gyro_sf_dmp(struct inv_mpu_state *st) +{ + int result; + u8 sampleDivider; + u32 gyro_sf; + const u32 gyro_sens = 0x03e80000; + + sampleDivider = st->sample_divider; + gyro_sf = inv_q30_mult(gyro_sens, + (int)(DMP_TAP_SCALE * (sampleDivider + 1))); + result = write_be32_key_to_mem(st, gyro_sf, KEY_D_0_104); + + return result; +} + +/* + * inv_set_shake_reject_thresh_dmp(): + * The gyro threshold, in dps, above which taps will be rejected. + */ +static int inv_set_shake_reject_thresh_dmp(struct inv_mpu_state *st, + int thresh) +{ + int result; + u8 sampleDivider; + int thresh_scaled; + u32 gyro_sf; + const u32 gyro_sens = 0x03e80000; + + sampleDivider = st->sample_divider; + gyro_sf = inv_q30_mult(gyro_sens, (int)(DMP_TAP_SCALE * + (sampleDivider + 1))); + /* We're in units of DPS, convert it back to chip units*/ + /*split the operation to aviod overflow of integer*/ + thresh_scaled = gyro_sens / (1L << 16); + thresh_scaled = thresh_scaled / thresh; + thresh_scaled = gyro_sf / thresh_scaled; + result = write_be32_key_to_mem(st, thresh_scaled, + KEY_DMP_TAP_SHAKE_REJECT); + + return result; +} + +/* + * inv_set_shake_reject_time_dmp(): + * How long a gyro axis must remain above its threshold + * before taps are rejected. + */ +static int inv_set_shake_reject_time_dmp(struct inv_mpu_state *st, + u32 time) +{ + int result; + u16 dmpTime; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + result = inv_write_2bytes(st, KEY_DMP_TAP_SHAKE_COUNT_MAX, dmpTime); + + return result; +} + +/* + * inv_set_shake_reject_timeout_dmp(): + * How long the gyros must remain below their threshold, + * after taps have been rejected, before taps can be detected again. + */ +static int inv_set_shake_reject_timeout_dmp(struct inv_mpu_state *st, + u32 time) +{ + int result; + u16 dmpTime; + u8 sampleDivider; + + sampleDivider = st->sample_divider; + sampleDivider++; + + /* 60 ms minimum time added */ + dmpTime = ((time) / sampleDivider); + result = inv_write_2bytes(st, KEY_DMP_TAP_SHAKE_TIMEOUT_MAX, dmpTime); + + return result; +} + +int inv_set_interrupt_on_gesture_event(struct inv_mpu_state *st, bool on) +{ + u8 r; + const u8 rn[] = {0xA3}; + const u8 rf[] = {0xFE}; + + if (on) + r = mem_w_key(KEY_CFG_FIFO_INT, ARRAY_SIZE(rn), rn); + else + r = mem_w_key(KEY_CFG_FIFO_INT, ARRAY_SIZE(rf), rf); + + return r; +} + +/* + * inv_enable_tap_dmp() - calling this function will enable/disable tap + * function. + */ +int inv_enable_tap_dmp(struct inv_mpu_state *st, bool on) +{ + int result; + + result = inv_set_tap_interrupt_dmp(st, on); + if (result) + return result; + result = inv_set_tap_threshold_dmp(st, st->tap.thresh); + if (result) + return result; + + result = inv_set_min_taps_dmp(st, st->tap.min_count); + if (result) + return result; + + result = inv_set_tap_time_dmp(st, st->tap.time); + if (result) + return result; + + result = inv_set_multiple_tap_time_dmp(st, DMP_MULTI_TAP_TIME); + if (result) + return result; + + result = inv_set_gyro_sf_dmp(st); + if (result) + return result; + + result = inv_set_shake_reject_thresh_dmp(st, DMP_SHAKE_REJECT_THRESH); + if (result) + return result; + + result = inv_set_shake_reject_time_dmp(st, DMP_SHAKE_REJECT_TIME); + if (result) + return result; + + result = inv_set_shake_reject_timeout_dmp(st, + DMP_SHAKE_REJECT_TIMEOUT); + return result; +} + +static int inv_dry_run_dmp(struct inv_mpu_state *st) +{ + int result; + struct inv_reg_map_s *reg; + + reg = &st->reg; + result = st->switch_gyro_engine(st, true); + if (result) + return result; + result = inv_i2c_single_write(st, reg->user_ctrl, BIT_DMP_EN); + if (result) + return result; + msleep(400); + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + if (result) + return result; + result = st->switch_gyro_engine(st, false); + if (result) + return result; + + return 0; +} + +static void inv_test_reset(struct inv_mpu_state *st) +{ + int result, ii; + u8 d[0x80]; + + for (ii = 0; ii < 0x80; ii++) { + /* don't read fifo r/w register */ + if (ii == st->reg.fifo_r_w) + d[0] = 0; + else + inv_i2c_read(st, ii, 1, &d[ii]); + } + result = inv_i2c_single_write(st, st->reg.pwr_mgmt_1, BIT_H_RESET); + if (result) + return; + msleep(POWER_UP_TIME); + + for (ii = 0; ii < 0x80; ii++) { + /* don't read fifo r/w register */ + if (ii == st->reg.fifo_r_w) + d[0] = 0; + else + result = inv_i2c_single_write(st, ii, d[ii]); + } +} + +/* + * inv_dmp_firmware_write() - calling this function will load the firmware. + * This is the write function of file "dmp_firmware". + */ +ssize_t inv_dmp_firmware_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t size) +{ + u8 *firmware; + int result; + struct inv_reg_map_s *reg; + struct iio_dev *indio_dev; + struct inv_mpu_state *st; + + indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj)); + st = iio_priv(indio_dev); + + if (st->chip_config.firmware_loaded) + return -EINVAL; + if (st->chip_config.enable) + return -EBUSY; + + reg = &st->reg; + if (DMP_IMAGE_SIZE != size) { + pr_err("wrong DMP image size - expected %d, actual %d\n", + DMP_IMAGE_SIZE, size); + return -EINVAL; + } + + firmware = kmalloc(size, GFP_KERNEL); + if (!firmware) + return -ENOMEM; + + mutex_lock(&indio_dev->mlock); + + memcpy(firmware, buf, size); + result = crc32(CRC_FIRMWARE_SEED, firmware, size); + if (DMP_IMAGE_CRC_VALUE != result) { + pr_err("firmware CRC error - 0x%08x vs 0x%08x\n", + result, DMP_IMAGE_CRC_VALUE); + result = -EINVAL; + goto firmware_write_fail; + } + + result = st->set_power_state(st, true); + if (result) + goto firmware_write_fail; + inv_test_reset(st); + + result = inv_load_firmware(st, firmware, size); + if (result) + goto firmware_write_fail; + + result = inv_verify_firmware(st, firmware, size); + if (result) + goto firmware_write_fail; + + result = inv_i2c_single_write(st, reg->prgm_strt_addrh, + st->chip_config.prog_start_addr >> 8); + if (result) + goto firmware_write_fail; + result = inv_i2c_single_write(st, reg->prgm_strt_addrh + 1, + st->chip_config.prog_start_addr & 0xff); + if (result) + goto firmware_write_fail; + + result = inv_gyro_dmp_cal(st); + if (result) + goto firmware_write_fail; + result = inv_accel_dmp_cal(st); + if (result) + goto firmware_write_fail; + result = inv_dry_run_dmp(st); + if (result) + goto firmware_write_fail; + + st->chip_config.firmware_loaded = 1; + +firmware_write_fail: + result |= st->set_power_state(st, false); + mutex_unlock(&indio_dev->mlock); + kfree(firmware); + if (result) + return result; + + return size; +} + +ssize_t inv_dmp_firmware_read(struct file *filp, + struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + int bank, write_size, size, data, result; + u16 memaddr; + struct iio_dev *indio_dev; + struct inv_mpu_state *st; + + size = count; + indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj)); + st = iio_priv(indio_dev); + + data = 0; + mutex_lock(&indio_dev->mlock); + if (!st->chip_config.enable) { + result = st->set_power_state(st, true); + if (result) { + mutex_unlock(&indio_dev->mlock); + return result; + } + } + for (bank = 0; size > 0; bank++, size -= write_size, + data += write_size) { + if (size > MPU_MEM_BANK_SIZE) + write_size = MPU_MEM_BANK_SIZE; + else + write_size = size; + + memaddr = (bank << 8); + result = mpu_memory_read(st, + st->i2c_addr, memaddr, write_size, &buf[data]); + if (result) { + mutex_unlock(&indio_dev->mlock); + return result; + } + } + if (!st->chip_config.enable) + result = st->set_power_state(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return count; +} + +ssize_t inv_six_q_write(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t pos, size_t size) +{ + u8 q[QUATERNION_BYTES]; + struct inv_reg_map_s *reg; + struct iio_dev *indio_dev; + struct inv_mpu_state *st; + int result; + + indio_dev = dev_get_drvdata(container_of(kobj, struct device, kobj)); + st = iio_priv(indio_dev); + + mutex_lock(&indio_dev->mlock); + + if (!st->chip_config.firmware_loaded) { + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + if (st->chip_config.enable) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + reg = &st->reg; + if (QUATERNION_BYTES != size) { + pr_err("wrong quaternion size=%d, should=%d\n", size, + QUATERNION_BYTES); + mutex_unlock(&indio_dev->mlock); + return -EINVAL; + } + + memcpy(q, buf, size); + result = st->set_power_state(st, true); + if (result) + goto firmware_write_fail; + result = mem_w_key(KEY_DMP_Q0, QUATERNION_BYTES, q); + +firmware_write_fail: + result |= st->set_power_state(st, false); + mutex_unlock(&indio_dev->mlock); + if (result) + return result; + + return size; +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu_ring.c b/drivers/iio/imu/inv_mpu9250/inv_mpu_ring.c new file mode 100644 index 0000000000000..2865bd81f4783 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu_ring.c @@ -0,0 +1,1965 @@ +/* +* 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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iio.h" +#include "kfifo_buf.h" +#include "trigger_consumer.h" +#include "sysfs.h" + +#include "inv_mpu_iio.h" + +static u8 fifo_data[HARDWARE_FIFO_SIZE + HEADERED_Q_BYTES]; +static int inv_process_batchmode(struct inv_mpu_state *st, bool insert); + +static int inv_push_8bytes_buffer(struct inv_mpu_state *st, u16 hdr, + u64 t, s16 *d) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int i; + + memcpy(buf, &hdr, sizeof(hdr)); + for (i = 0; i < 3; i++) + memcpy(&buf[2 + i * 2], &d[i], sizeof(d[i])); + iio_push_to_buffer(indio_dev->buffer, buf, 0); + memcpy(buf, &t, sizeof(t)); + iio_push_to_buffer(indio_dev->buffer, buf, 0); + + return 0; +} + +static int inv_push_16bytes_buffer(struct inv_mpu_state *st, u16 hdr, u64 t, + int *q) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u8 buf[IIO_BUFFER_BYTES]; + int i; + + memcpy(buf, &hdr, sizeof(hdr)); + memcpy(buf + 4, &q[0], sizeof(q[0])); + iio_push_to_buffer(indio_dev->buffer, buf, 0); + for (i = 0; i < 2; i++) + memcpy(buf + 4 * i, &q[i + 1], sizeof(q[i])); + iio_push_to_buffer(indio_dev->buffer, buf, 0); + memcpy(buf, &t, sizeof(t)); + iio_push_to_buffer(indio_dev->buffer, buf, 0); + + return 0; +} + +static int inv_send_pressure_data(struct inv_mpu_state *st) +{ + short sen[3]; + struct inv_chip_config_s *conf; + struct inv_mpu_slave *slave; + u64 curr_ts; + + conf = &st->chip_config; + slave = st->slave_pressure; + if (!st->sensor[SENSOR_PRESSURE].on) + return 0; + if (conf->dmp_on && conf->dmp_event_int_on) + return 0; + if (!conf->normal_pressure_measure) { + conf->normal_pressure_measure = 1; + return 0; + } + curr_ts = get_time_ns(); + if (curr_ts - slave->prev_ts > slave->min_read_time) { + slave->read_data(st, sen); + inv_push_8bytes_buffer(st, PRESSURE_HDR, st->last_ts, sen); + slave->prev_ts = curr_ts; + } + + return 0; +} + +static int inv_send_compass_data(struct inv_mpu_state *st) +{ + short sen[3]; + struct inv_chip_config_s *conf; + struct inv_mpu_slave *slave; + u64 curr_ts; + + conf = &st->chip_config; + slave = st->slave_compass; + if (!st->sensor[SENSOR_COMPASS].on) + return 0; + if (conf->dmp_on && conf->dmp_event_int_on) + return 0; + if (!conf->normal_compass_measure) { + conf->normal_compass_measure = 1; + return 0; + } + curr_ts = get_time_ns(); + if (curr_ts - slave->prev_ts > slave->min_read_time) { + slave->read_data(st, sen); + inv_push_8bytes_buffer(st, COMPASS_HDR, st->last_ts, sen); + slave->prev_ts = curr_ts; + } + + return 0; +} + +static void process_step_only_batch(struct inv_mpu_state *st) +{ + int target_bytes, tmp, r; + u8 *dptr, *d; + u16 hdr; + s16 sen[3]; + u64 t; + + d = fifo_data; + dptr = d; + + if (st->fifo_count == 0) + return; + + target_bytes = st->fifo_count; + while (target_bytes > 0) { + if (target_bytes < MAX_READ_SIZE) + tmp = target_bytes; + else + tmp = MAX_READ_SIZE; + r = inv_i2c_read(st, st->reg.fifo_r_w, tmp, dptr); + if (r < 0) + return; + dptr += tmp; + target_bytes -= tmp; + } + dptr = d; + target_bytes = st->fifo_count; + while (dptr - d <= target_bytes - HEADERED_NORMAL_BYTES) { + hdr = (u16)be16_to_cpup((__be16 *)(dptr)); + switch (hdr & (~1)) { + case STEP_DETECTOR_HDR: + tmp = (int)be32_to_cpup((__be32 *)(dptr + 4)); + t = st->step_detector_base_ts + + (u64)tmp * 5 * NSEC_PER_MSEC; + sen[0] = sen[1] = sen[2] = 0; + inv_push_8bytes_buffer(st, hdr, t, sen); + break; + default: + break; + } + dptr += HEADERED_NORMAL_BYTES; + } + + return; +} +static int inv_batchmode_calc(struct inv_mpu_state *st) +{ + int b, c, num_rate, rate; + int i, j, timeout, num_sensor_on, timeout_count; + bool int_on; + + num_sensor_on = 0; + num_rate = 0; + rate = 0; + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) { + if (i != SENSOR_STEP) { + if (num_rate == 0) { + rate = st->sensor[i].rate; + num_rate++; + } else if (rate != st->sensor[i].rate) { + num_rate++; + } + num_sensor_on++; + } + } + } + if ((num_sensor_on == 0) && st->sensor[SENSOR_STEP].on) { + /* in step only batchmode, counter is fixed */ + st->batch.counter = FIFO_SIZE / 8; + st->batch.step_only = true; + st->batch.on = true; + + return 0; + } else if (num_sensor_on == 0) { + return 0; + } + + b = st->batch.timeout * st->bytes_per_sec; + if ((b > (FIFO_SIZE * ONE_K_HZ)) && (!st->batch.overflow_on)) + timeout = FIFO_SIZE * ONE_K_HZ / st->bytes_per_sec; + else + timeout = st->batch.timeout; + + /* 5 ms per count */ + timeout_count = timeout / 5; + if (num_rate == 1) { + c = timeout * rate / ONE_K_HZ; + } else { + c = 0; + for (i = 1; i <= timeout_count; i++) { + int_on = false; + for (j = 0; j < SENSOR_NUM_MAX; j++) { + if (st->sensor[j].on) { + if ((i % st->sensor[j].counter) == 0) + int_on = true; + } + } + if (int_on) + c++; + } + } + st->batch.counter = c; + st->batch.on = true; + + return 0; +} + +int inv_batchmode_setup(struct inv_mpu_state *st) +{ + int r; + + r = inv_write_2bytes(st, KEY_BM_NUMWORD_TOFILL, 0); + if (r) + return r; + r = inv_write_2bytes(st, KEY_BM_BATCH_CNTR, 0); + if (r) + return r; + + if (st->chip_config.dmp_on && (st->batch.timeout > 0) && + (st->chip_config.dmp_event_int_on == 0)) { + r = inv_batchmode_calc(st); + if (r) + return r; + } + + if (st->batch.on) { + r = inv_write_2bytes(st, KEY_BM_BATCH_THLD, st->batch.counter); + if (r) + return r; + } + r = inv_write_2bytes(st, KEY_BM_ENABLE, st->batch.on); + + return r; +} + +/** + * reset_fifo_mpu3050() - Reset FIFO related registers + * @indio_dev: Device driver instance. + */ +static int reset_fifo_mpu3050(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result; + u8 val, user_ctrl; + struct inv_mpu_state *st = iio_priv(indio_dev); + reg = &st->reg; + + /* disable interrupt */ + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config); + if (result) + return result; + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + result = inv_i2c_read(st, reg->user_ctrl, 1, &user_ctrl); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + user_ctrl &= ~BIT_FIFO_EN; + st->chip_config.has_footer = 0; + /* reset fifo */ + val = (BIT_3050_FIFO_RST | user_ctrl); + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + if (st->chip_config.dmp_on) { + /* enable interrupt when DMP is done */ + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | BIT_DMP_INT_EN); + if (result) + return result; + + result = inv_i2c_single_write(st, reg->user_ctrl, + BIT_FIFO_EN|user_ctrl); + if (result) + return result; + } else { + /* enable interrupt */ + if (st->sensor[SENSOR_ACCEL].on || + st->sensor[SENSOR_GYRO].on) { + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | BIT_DATA_RDY_EN); + if (result) + return result; + } + /* enable FIFO reading and I2C master interface*/ + result = inv_i2c_single_write(st, reg->user_ctrl, + BIT_FIFO_EN | user_ctrl); + if (result) + return result; + /* enable sensor output to FIFO and FIFO footer*/ + val = 1; + if (st->sensor[SENSOR_ACCEL].on) + val |= BITS_3050_ACCEL_OUT; + if (st->sensor[SENSOR_GYRO].on) + val |= BITS_GYRO_OUT; + result = inv_i2c_single_write(st, reg->fifo_en, val); + if (result) + return result; + } + + return 0; +reset_fifo_fail: + if (st->chip_config.dmp_on) + val = BIT_DMP_INT_EN; + else + val = BIT_DATA_RDY_EN; + inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config | val); + pr_err("reset fifo failed\n"); + + return result; +} + +/* + * inv_set_lpf() - set low pass filer based on fifo rate. + */ +static int inv_set_lpf(struct inv_mpu_state *st, int rate) +{ + const short hz[] = {188, 98, 42, 20, 10, 5}; + const int d[] = {INV_FILTER_188HZ, INV_FILTER_98HZ, + INV_FILTER_42HZ, INV_FILTER_20HZ, + INV_FILTER_10HZ, INV_FILTER_5HZ}; + int i, h, data, result; + struct inv_reg_map_s *reg; + + reg = &st->reg; + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(d) - 1)) + i++; + data = d[i]; + if (INV_MPU3050 == st->chip_type) { + if (st->slave_accel != NULL) { + result = st->slave_accel->set_lpf(st, rate); + if (result) + return result; + } + result = inv_i2c_single_write(st, reg->lpf, data | + (st->chip_config.fsr << GYRO_CONFIG_FSR_SHIFT)); + } else { + result = inv_i2c_single_write(st, reg->lpf, data); + } + if (result) + return result; + st->chip_config.lpf = data; + + return 0; +} + +/* + * set_fifo_rate_reg() - Set fifo rate in hardware register + */ +static int set_fifo_rate_reg(struct inv_mpu_state *st) +{ + u8 data; + u16 fifo_rate; + int result; + struct inv_reg_map_s *reg; + + reg = &st->reg; + fifo_rate = st->chip_config.new_fifo_rate; + data = ONE_K_HZ / fifo_rate - 1; + result = inv_i2c_single_write(st, reg->sample_rate_div, data); + if (result) + return result; + result = inv_set_lpf(st, fifo_rate); + if (result) + return result; + /* wait for the sampling rate change to stabilize */ + mdelay(INV_MPU_SAMPLE_RATE_CHANGE_STABLE); + st->chip_config.fifo_rate = fifo_rate; + + return 0; +} + +/* + * inv_lpa_mode() - store current low power mode settings + */ +static int inv_lpa_mode(struct inv_mpu_state *st, int lpa_mode) +{ + unsigned long result; + u8 d; + struct inv_reg_map_s *reg; + + reg = &st->reg; + result = inv_i2c_read(st, reg->pwr_mgmt_1, 1, &d); + if (result) + return result; + if (lpa_mode) + d |= BIT_CYCLE; + else + d &= ~BIT_CYCLE; + + result = inv_i2c_single_write(st, reg->pwr_mgmt_1, d); + if (result) + return result; + if (INV_MPU6500 == st->chip_type) { + d = BIT_FIFO_SIZE_1K; + if (lpa_mode) + d |= BIT_ACCEL_FCHOCIE_B; + result = inv_i2c_single_write(st, REG_6500_ACCEL_CONFIG2, d); + if (result) + return result; + } + + return 0; +} + +static int inv_set_master_delay(struct inv_mpu_state *st) +{ + int d, result, rate; + u8 delay; + + if ((!st->sensor[SENSOR_COMPASS].on) && + (!st->sensor[SENSOR_PRESSURE].on)) + return 0; + + delay = 0; + d = 0; + if (st->sensor[SENSOR_COMPASS].on) { + switch (st->plat_data.sec_slave_id) { + case COMPASS_ID_AK8975: + case COMPASS_ID_AK8972: + case COMPASS_ID_AK8963: + delay = (BIT_SLV0_DLY_EN | BIT_SLV1_DLY_EN); + break; + case COMPASS_ID_MLX90399: + delay = (BIT_SLV0_DLY_EN | + BIT_SLV1_DLY_EN | + BIT_SLV2_DLY_EN | + BIT_SLV3_DLY_EN); + break; + default: + return -EINVAL; + } + d = max(d, st->slave_compass->rate_scale); + } + if (st->sensor[SENSOR_PRESSURE].on) { + /* read fake data when compass is disabled for DMP read */ + if (!st->sensor[SENSOR_COMPASS].on) + delay |= BIT_SLV0_DLY_EN; + switch (st->plat_data.aux_slave_id) { + case PRESSURE_ID_BMP280: + delay |= (BIT_SLV2_DLY_EN | BIT_SLV3_DLY_EN); + break; + default: + return -EINVAL; + } + d = max(d, st->slave_pressure->rate_scale); + } + result = inv_i2c_single_write(st, REG_I2C_MST_DELAY_CTRL, delay); + if (result) + return result; + + d = d * st->chip_config.fifo_rate / ONE_K_HZ; + if (st->chip_config.dmp_on) { + rate = 0; + if (st->sensor[SENSOR_PRESSURE].on) + rate = max(rate, st->sensor[SENSOR_PRESSURE].rate); + if (st->sensor[SENSOR_COMPASS].on) + rate = max(rate, st->sensor[SENSOR_COMPASS].rate); + if (rate == 0) + return -EINVAL; + d = max(d, st->chip_config.fifo_rate / rate); + } + + if (d > 0) + d -= 1; + if (d > 0x1F) + d = 0x1F; + + /* I2C_MST_DLY is set to slow down secondary I2C */ + return inv_i2c_single_write(st, REG_I2C_SLV4_CTRL, d); +} + +/* + * reset_fifo_itg() - Reset FIFO related registers. + */ +static int reset_fifo_itg(struct iio_dev *indio_dev) +{ + struct inv_reg_map_s *reg; + int result, i; + u8 val, int_word; + struct inv_mpu_state *st = iio_priv(indio_dev); + + reg = &st->reg; + if (st->chip_config.lpa_mode) { + result = inv_lpa_mode(st, 0); + if (result) { + pr_err("reset lpa mode failed\n"); + return result; + } + } + /* disable interrupt */ + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) { + pr_err("int_enable write failed\n"); + return result; + } + /* disable the sensor output to FIFO */ + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + goto reset_fifo_fail; + /* disable fifo reading */ + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + if (result) + goto reset_fifo_fail; + int_word = 0; + + /* MPU6500's BIT_6500_WOM_EN is the same as BIT_MOT_EN */ + if (st->mot_int.mot_on) + int_word |= BIT_MOT_EN; + + if (st->chip_config.dmp_on) { + val = (BIT_FIFO_RST | BIT_DMP_RST); + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + if (st->chip_config.dmp_int_on) { + int_word |= BIT_DMP_INT_EN; + result = inv_i2c_single_write(st, reg->int_enable, + int_word); + if (result) + return result; + } + val = (BIT_DMP_EN | BIT_FIFO_EN); + if ((st->sensor[SENSOR_COMPASS].on || + st->sensor[SENSOR_PRESSURE].on) && + (!st->chip_config.dmp_event_int_on)) + val |= BIT_I2C_MST_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + } else { + /* reset FIFO and possibly reset I2C*/ + val = BIT_FIFO_RST; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + /* enable interrupt */ + if (st->sensor[SENSOR_ACCEL].on || + st->sensor[SENSOR_GYRO].on || + st->sensor[SENSOR_COMPASS].on || + st->sensor[SENSOR_PRESSURE].on) + int_word |= BIT_DATA_RDY_EN; + + result = inv_i2c_single_write(st, reg->int_enable, int_word); + if (result) + return result; + /* enable FIFO reading and I2C master interface*/ + val = BIT_FIFO_EN; + if (st->sensor[SENSOR_COMPASS].on || + st->sensor[SENSOR_PRESSURE].on) + val |= BIT_I2C_MST_EN; + result = inv_i2c_single_write(st, reg->user_ctrl, val); + if (result) + goto reset_fifo_fail; + /* enable sensor output to FIFO */ + val = 0; + if (st->sensor[SENSOR_GYRO].on) + val |= BITS_GYRO_OUT; + if (st->sensor[SENSOR_ACCEL].on) + val |= BIT_ACCEL_OUT; + result = inv_i2c_single_write(st, reg->fifo_en, val); + if (result) + goto reset_fifo_fail; + } + result = inv_set_master_delay(st); + if (result) + goto reset_fifo_fail; + st->last_ts = get_time_ns(); + st->prev_ts = st->last_ts; + st->last_run_time = st->last_ts; + if (st->sensor[SENSOR_COMPASS].on) + st->slave_compass->prev_ts = st->last_ts; + if (st->sensor[SENSOR_PRESSURE].on) + st->slave_pressure->prev_ts = st->last_ts; + + st->dmp_interval = DMP_INTERVAL_INIT; + st->ts_counter = 0; + st->diff_accumulater = 0; + st->dmp_interval_accum = 0; + st->step_detector_base_ts = st->last_ts; + st->chip_config.normal_compass_measure = 0; + st->chip_config.normal_pressure_measure = 0; + st->left_over_size = 0; + st->batch.post_isr_run = false; + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].ts = st->last_ts; + + result = inv_lpa_mode(st, st->chip_config.lpa_mode); + if (result) + goto reset_fifo_fail; + + return 0; + +reset_fifo_fail: + if (st->chip_config.dmp_on) + val = BIT_DMP_INT_EN; + else + val = BIT_DATA_RDY_EN; + inv_i2c_single_write(st, reg->int_enable, val); + pr_err("reset fifo failed\n"); + + return result; +} + +/** + * inv_clear_kfifo() - clear time stamp fifo + * @st: Device driver instance. + */ +static void inv_clear_kfifo(struct inv_mpu_state *st) +{ + unsigned long flags; + + spin_lock_irqsave(&st->time_stamp_lock, flags); + kfifo_reset(&st->timestamps); + spin_unlock_irqrestore(&st->time_stamp_lock, flags); +} + +/* + * inv_reset_fifo() - Reset FIFO related registers. + */ +int inv_reset_fifo(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + + inv_clear_kfifo(st); + if (INV_MPU3050 == st->chip_type) + return reset_fifo_mpu3050(indio_dev); + else + return reset_fifo_itg(indio_dev); +} + +static int inv_send_gyro_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x12}; + int result; + u8 *r; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_GYRO, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_accel_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x12}; + int result; + u8 *r; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_ACCL, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_three_q_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x13}; + int result; + u8 *r; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_3QUAT, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_six_q_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x13}; + int result; + u8 *r; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_QUAT, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_ped_q_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x12}; + u8 *r; + int result; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_PQUAT, ARRAY_SIZE(rn), r); + + return result; +} + +static int inv_add_step_indicator(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xf3, 0xf3}; + u8 rf[] = {0xf4, 0x03}; + int result; + u8 *r; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_PEDSTEP_DET, ARRAY_SIZE(rn), r); + + return result; +} + +static int inv_send_compass_dmp_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x12}; + u8 *r; + int result; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_CPASS, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_pressure_dmp_data(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x12}; + u8 *r; + int result; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_PRESS, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_send_step_detector(struct inv_mpu_state *st, bool on) +{ + u8 rn[] = {0xa3, 0xa3}; + u8 rf[] = {0xf4, 0x0e}; + u8 *r; + int result; + + if (on) + r = rn; + else + r = rf; + result = mem_w_key(KEY_CFG_OUT_STEPDET, ARRAY_SIZE(rf), r); + + return result; +} + +static int inv_set_rate(struct inv_mpu_state *st, int k, int k_ct, int rate) +{ + int v, result; + + v = MPU_DEFAULT_DMP_FREQ / rate - 1; + result = inv_write_2bytes(st, k, v); + if (result) + return result; + result = inv_write_2bytes(st, k_ct, 0); + + return result; +} + +static int inv_set_gyro_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_GYRO_ODR, KEY_ODR_CNTR_GYRO, + st->sensor[SENSOR_GYRO].rate); + + return result; +} + +static int inv_set_accel_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_ACCL_ODR, KEY_ODR_CNTR_ACCL, + st->sensor[SENSOR_ACCEL].rate); + + return result; +} + +static int inv_set_compass_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_CPASS_ODR, KEY_ODR_CNTR_CPASS, + st->sensor[SENSOR_COMPASS].rate); + + return result; +} + +static int inv_set_pressure_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_PRESS_ODR, KEY_ODR_CNTR_PRESS, + st->sensor[SENSOR_PRESSURE].rate); + + return result; +} + +static int inv_set_step_detector(struct inv_mpu_state *st) +{ + return 0; +} + + +static int inv_set_lpq_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_3QUAT_ODR, KEY_ODR_CNTR_3QUAT, + st->sensor[SENSOR_LPQ].rate); + + return result; +} + +static int inv_set_sixq_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_QUAT_ODR, KEY_ODR_CNTR_QUAT, + st->sensor[SENSOR_SIXQ].rate); + + return result; +} + +static int inv_set_pedq_rate(struct inv_mpu_state *st) +{ + int result; + + result = inv_set_rate(st, KEY_CFG_PQUAT_ODR, KEY_ODR_CNTR_PQUAT, + st->sensor[SENSOR_PEDQ].rate); + + return result; +} + + +static int inv_set_dmp_sysfs(struct inv_mpu_state *st) +{ + int result, i, s; + u8 d[] = {0, 0, 0, 0}; + + result = inv_set_interrupt_on_gesture_event(st, + st->chip_config.dmp_event_int_on); + if (st->chip_config.dmp_event_int_on) { + for (i = 0; i < SENSOR_NUM_MAX; i++) { + result = st->sensor[i].send_data(st, false); + if (result) + return result; + } + } else { + s = 0; + for (i = 0; i < SENSOR_NUM_MAX; i++) { + result = st->sensor[i].send_data(st, st->sensor[i].on); + if (result) + return result; + if (st->sensor[i].on) { + if (0 == st->sensor[i].rate) + return -EINVAL; + s += st->sensor[i].rate * + st->sensor[i].sample_size; + + result = st->sensor[i].set_rate(st); + if (result) + return result; + st->sensor[i].counter = MPU_DEFAULT_DMP_FREQ / + st->sensor[i].rate; + } + } + st->bytes_per_sec = s; + if (st->sensor[SENSOR_STEP].on) + result = inv_add_step_indicator(st, true); + else + result = inv_add_step_indicator(st, + st->chip_config.step_indicator_on); + if (result) + return result; + } + result = inv_batchmode_setup(st); + if (result) + return result; + + st->dmp_counter = 0; + result = mem_w_key(KEY_DMP_RUN_CNTR, ARRAY_SIZE(d), d); + if (result) + return result; + /* set the compass flag invalid at the beginning */ + result = inv_write_2bytes(st, KEY_CPASS_VALID, 0); + + return result; +} + +static void inv_get_data_count(struct inv_mpu_state *st) +{ + struct inv_chip_config_s *c; + int b, i; + + c = &st->chip_config; + b = 0; + if (st->chip_config.dmp_on) { + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (st->sensor[i].on) + b += st->sensor[i].sample_size; + } + } else { + if (st->sensor[SENSOR_ACCEL].on) + b += BYTES_PER_SENSOR; + if (st->sensor[SENSOR_GYRO].on) + b += BYTES_PER_SENSOR; + } + c->bytes_per_datum = b; + + return; +} +/* + * set_inv_enable() - main enable/disable function. + */ +int set_inv_enable(struct iio_dev *indio_dev, bool enable) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + u8 data[2]; + int result; + + reg = &st->reg; + if (enable) { + st->batch.on = false; + st->batch.step_only = false; + + inv_get_data_count(st); + if (st->chip_config.new_fifo_rate != + st->chip_config.fifo_rate) { + result = set_fifo_rate_reg(st); + if (result) + return result; + } + if (st->chip_config.dmp_on) { + result = inv_set_dmp_sysfs(st); + if (result) + return result; + } + + if (st->chip_config.gyro_enable) { + result = st->switch_gyro_engine(st, true); + if (result) + return result; + } + if (st->chip_config.accel_enable) { + result = st->switch_accel_engine(st, true); + if (result) + return result; + } + if (st->sensor[SENSOR_COMPASS].on) { + result = st->slave_compass->resume(st); + if (result) + return result; + } + if (st->sensor[SENSOR_PRESSURE].on) { + result = st->slave_pressure->resume(st); + if (result) + return result; + } + result = inv_reset_fifo(indio_dev); + if (result) + return result; + if (st->batch.step_only) + schedule_delayed_work(&st->work, + msecs_to_jiffies(st->batch.timeout)); + + } else { + if (st->batch.step_only) + cancel_delayed_work_sync(&st->work); + if ((INV_MPU3050 != st->chip_type) + && st->chip_config.lpa_mode) { + /* if the chip is in low power mode, + register write/read could fail */ + result = inv_lpa_mode(st, 0); + if (result) + return result; + } + result = inv_i2c_single_write(st, reg->fifo_en, 0); + if (result) + return result; + if (st->chip_config.dmp_on) { + result = inv_read_time_and_ticks(st, false); + if (result) + return result; + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + return result; + st->fifo_count = be16_to_cpup((__be16 *)(data)); + result = inv_process_batchmode(st, true); + if (result) + return result; + } + /* disable fifo reading */ + if (INV_MPU3050 != st->chip_type) { + result = inv_i2c_single_write(st, reg->int_enable, 0); + if (result) + return result; + result = inv_i2c_single_write(st, reg->user_ctrl, 0); + } else { + result = inv_i2c_single_write(st, reg->int_enable, + st->plat_data.int_config); + } + if (result) + return result; + /* turn off the gyro/accel engine during disable phase */ + result = st->switch_gyro_engine(st, false); + if (result) + return result; + result = st->switch_accel_engine(st, false); + if (result) + return result; + if (st->sensor[SENSOR_COMPASS].on) { + result = st->slave_compass->suspend(st); + if (result) + return result; + } + if (st->sensor[SENSOR_PRESSURE].on) { + result = st->slave_pressure->suspend(st); + if (result) + return result; + } + } + st->chip_config.enable = enable; + + return 0; +} + +/* + * inv_irq_handler() - Cache a timestamp at each data ready interrupt. + */ +static irqreturn_t inv_irq_handler(int irq, void *dev_id) +{ + struct inv_mpu_state *st = (struct inv_mpu_state *)dev_id; + u64 ts; + + if (!st->chip_config.dmp_on) { + ts = get_time_ns(); + kfifo_in_spinlocked(&st->timestamps, &ts, 1, + &st->time_stamp_lock); + } + + return IRQ_WAKE_THREAD; +} + +static void inv_report_data_3050(struct iio_dev *indio_dev, s64 t, + int has_footer, u8 *data) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + int ind, i; + short s[THREE_AXIS]; + + ind = 0; + if (has_footer) + ind += 2; + + if (st->sensor[SENSOR_GYRO].on) { + for (i = 0; i < 3; i++) + s[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + + inv_push_8bytes_buffer(st, GYRO_HDR, t, s); + ind += BYTES_PER_SENSOR; + } + if (st->sensor[SENSOR_ACCEL].on) { + st->slave_accel->combine_data(&data[ind], s); + inv_push_8bytes_buffer(st, ACCEL_HDR, t, s); + } +} + +/* + * inv_read_fifo_mpu3050() - Transfer data from FIFO to ring buffer for + * mpu3050. + */ +irqreturn_t inv_read_fifo_mpu3050(int irq, void *dev_id) +{ + + struct inv_mpu_state *st = (struct inv_mpu_state *)dev_id; + struct iio_dev *indio_dev = iio_priv_to_dev(st); + int bytes_per_datum; + u8 data[64]; + int result; + short fifo_count, byte_read; + s64 timestamp; + struct inv_reg_map_s *reg; + + reg = &st->reg; + mutex_lock(&indio_dev->mlock); + if (st->chip_config.dmp_on) + bytes_per_datum = HEADERED_NORMAL_BYTES; + else + bytes_per_datum = (st->sensor[SENSOR_ACCEL].on + + st->sensor[SENSOR_GYRO].on) * BYTES_PER_SENSOR; + if (st->chip_config.has_footer) + byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE; + else + byte_read = bytes_per_datum; + + fifo_count = 0; + if (byte_read != 0) { + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(&data[0])); + if (fifo_count < byte_read) + goto end_session; + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count > FIFO_THRESHOLD) + goto flush_fifo; + /* Timestamp mismatch. */ + if (kfifo_len(&st->timestamps) < + fifo_count / byte_read) + goto flush_fifo; + if (kfifo_len(&st->timestamps) > + fifo_count / byte_read + TIME_STAMP_TOR) { + if (st->chip_config.dmp_on) { + result = kfifo_out(&st->timestamps, + ×tamp, 1); + if (result != 1) + goto flush_fifo; + } else { + goto flush_fifo; + } + } + } + while ((bytes_per_datum != 0) && (fifo_count >= byte_read)) { + result = inv_i2c_read(st, reg->fifo_r_w, byte_read, data); + if (result) + goto flush_fifo; + + result = kfifo_out(&st->timestamps, ×tamp, 1); + if (result != 1) + goto flush_fifo; + inv_report_data_3050(indio_dev, timestamp, + st->chip_config.has_footer, data); + fifo_count -= byte_read; + if (st->chip_config.has_footer == 0) { + st->chip_config.has_footer = 1; + byte_read = bytes_per_datum + MPU3050_FOOTER_SIZE; + } + } + +end_session: + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; + +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + inv_clear_kfifo(st); + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; +} + +static int inv_report_gyro_accel(struct iio_dev *indio_dev, + u8 *data, s64 t) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + short s[THREE_AXIS]; + int ind; + int i; + + ind = 0; + if (st->sensor[SENSOR_ACCEL].on) { + for (i = 0; i < 3; i++) + s[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + inv_push_8bytes_buffer(st, ACCEL_HDR, t, s); + ind += BYTES_PER_SENSOR; + } + + if (st->sensor[SENSOR_GYRO].on) { + for (i = 0; i < 3; i++) + s[i] = be16_to_cpup((__be16 *)(&data[ind + i * 2])); + inv_push_8bytes_buffer(st, GYRO_HDR, t, s); + ind += BYTES_PER_SENSOR; + } + + return 0; +} + +static void inv_process_motion(struct inv_mpu_state *st) +{ + struct iio_dev *indio_dev = iio_priv_to_dev(st); + int result; + u8 data[1]; + + /* motion interrupt */ + result = inv_i2c_read(st, REG_INT_STATUS, 1, data); + if (result) + return; + + if (data[0] & BIT_MOT_INT) + sysfs_notify(&indio_dev->dev.kobj, NULL, "event_accel_motion"); +} + +static int inv_get_timestamp(struct inv_mpu_state *st, int count) +{ + u32 *dur; + u32 thresh; + s32 diff, result, counter; + u64 ts; + + /* goal of algorithm is to estimate the true frequency of the chip */ + if (st->chip_config.dmp_on && st->chip_config.dmp_event_int_on) + return 0; + dur = &st->irq_dur_ns; + counter = 1; + thresh = min((u32)((*dur) >> 2), (u32)(10 * NSEC_PER_MSEC)); + while (kfifo_len(&st->timestamps) >= count) { + result = kfifo_out(&st->timestamps, &ts, 1); + if (result != 1) + return -EINVAL; + /* first time since reset fifo, just take it */ + if (!st->ts_counter) { + st->last_ts = ts; + st->prev_ts = ts; + st->ts_counter++; + return 0; + } + diff = (s32)(ts - st->prev_ts); + st->prev_ts = ts; + if (abs(diff - (*dur)) < thresh) { + st->diff_accumulater >>= 1; + if (*dur > diff) + st->diff_accumulater -= (((*dur) - diff) >> 7); + else + st->diff_accumulater += ((diff - (*dur)) >> 7); + *dur += st->diff_accumulater; + } + } + ts = *dur; + ts *= counter; + st->last_ts += ts; + + return 0; +} + +static int inv_process_dmp_interrupt(struct inv_mpu_state *st) +{ + int r; + u8 d[1]; + struct iio_dev *indio_dev = iio_priv_to_dev(st); + +#define DMP_INT_SMD 0x04 +#define DMP_INT_PED 0x08 +#define DMP_INT_TAP 0x10 + + if ((!st->chip_config.smd_enable) && + (!st->chip_config.ped_int_on) && + (!st->chip_config.tap_on)) + return 0; + + r = inv_i2c_read(st, REG_DMP_INT_STATUS, 1, d); + if (r) + return r; + if (d[0] & DMP_INT_SMD) { + sysfs_notify(&indio_dev->dev.kobj, NULL, "event_smd"); + st->chip_config.smd_enable = false; + st->chip_config.smd_triggered = true; + } + if (d[0] & DMP_INT_PED) + sysfs_notify(&indio_dev->dev.kobj, NULL, "event_pedometer"); + if (d[0] & DMP_INT_TAP) + sysfs_notify(&indio_dev->dev.kobj, NULL, "event_tap"); + + return 0; +} + +static int inv_get_shift2(int count) +{ + int i; + + if (1 == count) + return 13; + if (count > 2000) + return 2; + i = 13; + while (count > 0) { + count >>= 1; + i--; + } + + return i; +} + +static void inv_adjust_sensor_ts(struct inv_mpu_state *st, int sensor_ind) +{ + s64 diff; + int i, rate_adj, s3, delta, total_count; + + if (!st->chip_config.adjust_time) + return; +#define MAX_DIFF 0x7fffffff + total_count = st->dmp_ticks; + if (0 == total_count) + total_count = 1; + + diff = (st->last_ts - st->prev_ts) - (u64)(st->dmp_interval) * + total_count; + if (diff > MAX_DIFF) + diff = MAX_DIFF; + if (diff < -MAX_DIFF) + diff = -MAX_DIFF; + s3 = 4; + rate_adj = (int)diff; + rate_adj /= total_count; + delta = min(abs(rate_adj) >> inv_get_shift2(total_count), + DMP_INTERVAL_MIN_ADJ); + if (rate_adj < 0) + delta = -delta; + st->dmp_interval_accum >>= 1; + st->dmp_interval_accum += delta; + st->dmp_interval += st->dmp_interval_accum; + for (i = 0; i < SENSOR_NUM_MAX; i++) + if (st->sensor[i].on) + st->sensor[i].dur = st->dmp_interval * + st->sensor[i].counter; + + st->prev_ts = st->last_ts; +} + +static void inv_reset_ts(struct inv_mpu_state *st, u64 curr_ts) +{ + u32 dur, i; + + dur = USEC_PER_SEC / st->bytes_per_sec; + dur *= 1024; + curr_ts -= ((u64)dur * NSEC_PER_USEC); + for (i = 0; i < SENSOR_NUM_MAX; i++) + st->sensor[i].ts = curr_ts; +} + +static void inv_push_step_indicator(struct inv_mpu_state *st, int sensor_ind, + int steps) +{ + int dur, i; + s16 sen[3]; + u64 base; +#define STEP_INDICATOR_HEADER 0x0001 + + dur = st->sensor[sensor_ind].dur / steps; + base = st->sensor[sensor_ind].ts; + + for (i = 1; i < steps; i++) + inv_push_8bytes_buffer(st, STEP_INDICATOR_HEADER, + base + i * dur, sen); +} + +static int inv_parse_header(u16 hdr) +{ + int sensor_ind; + + switch (hdr) { + case ACCEL_HDR: + sensor_ind = SENSOR_ACCEL; + break; + case GYRO_HDR: + sensor_ind = SENSOR_GYRO; + break; + case PEDQUAT_HDR: + sensor_ind = SENSOR_PEDQ; + break; + case LPQUAT_HDR: + sensor_ind = SENSOR_LPQ; + break; + case SIXQUAT_HDR: + sensor_ind = SENSOR_SIXQ; + break; + case COMPASS_HDR: + sensor_ind = SENSOR_COMPASS; + break; + case PRESSURE_HDR: + sensor_ind = SENSOR_PRESSURE; + break; + case STEP_DETECTOR_HDR: + sensor_ind = SENSOR_STEP; + break; + default: + sensor_ind = SENSOR_INVALID; + break; + } + + return sensor_ind; +} + +static int inv_get_sensor_count(struct inv_mpu_state *st, + u8 *d, int target_bytes) +{ + u8 *dptr; + u16 hdr; + int sensor_ind; + int sensor_count; + + sensor_count = 0; + dptr = d; + while (dptr - d <= target_bytes - HEADERED_NORMAL_BYTES) { + hdr = (u16)be16_to_cpup((__be16 *)(dptr)); + hdr &= (~STEP_INDICATOR_MASK); + sensor_ind = inv_parse_header(hdr); + /* incomplete packet */ + if (target_bytes - (dptr - d) < + st->sensor[sensor_ind].sample_size) + break; + if ((sensor_ind != SENSOR_INVALID) && + st->sensor[sensor_ind].on) { + dptr += st->sensor[sensor_ind].sample_size; + sensor_count++; + } else { + dptr += HEADERED_NORMAL_BYTES; + } + } + + return sensor_count; +} +static int inv_process_batchmode(struct inv_mpu_state *st, bool insert) +{ + int i, target_bytes, tmp, res, sensor_count, counter; + int sensor_ind, q[3]; + u8 *dptr, *d; + u16 hdr, steps; + s16 sen[3]; + u64 t; + bool done_flag; + +#define END_MARKER 0x0010 + + if (1024 == st->fifo_count) { + inv_reset_ts(st, st->last_ts); + st->left_over_size = 0; + } + d = fifo_data; + if (st->left_over_size > 0) { + dptr = d + st->left_over_size; + memcpy(d, st->left_over, st->left_over_size); + } else { + dptr = d; + } + target_bytes = st->fifo_count; + while (target_bytes > 0) { + if (target_bytes < MAX_READ_SIZE) + tmp = target_bytes; + else + tmp = MAX_READ_SIZE; + res = inv_i2c_read(st, st->reg.fifo_r_w, tmp, dptr); + if (res < 0) + return res; + dptr += tmp; + target_bytes -= tmp; + } + dptr = d; + done_flag = false; + target_bytes = st->fifo_count + st->left_over_size; + if (insert) + sensor_count = inv_get_sensor_count(st, d, target_bytes); + else + sensor_count = 0; + counter = 0; + while ((dptr - d <= target_bytes - HEADERED_NORMAL_BYTES) && + (!done_flag)) { + hdr = (u16)be16_to_cpup((__be16 *)(dptr)); + steps = (hdr & STEP_INDICATOR_MASK); + hdr &= (~STEP_INDICATOR_MASK); + sensor_ind = inv_parse_header(hdr); + /* incomplete packet */ + if (target_bytes - (dptr - d) < + st->sensor[sensor_ind].sample_size) { + done_flag = true; + continue; + } + /* error packet */ + if ((sensor_ind == SENSOR_INVALID) || + (!st->sensor[sensor_ind].on)) { + dptr += HEADERED_NORMAL_BYTES; + continue; + } + if (insert) { + counter++; + if (counter == sensor_count) + hdr |= END_MARKER; + } + + if (sensor_ind == SENSOR_STEP) { + tmp = (int)be32_to_cpup((__be32 *)(dptr + 4)); + t = st->step_detector_base_ts + + (u64)tmp * 5 * NSEC_PER_MSEC; + inv_push_8bytes_buffer(st, hdr, t, sen); + dptr += HEADERED_NORMAL_BYTES; + continue; + } + if (steps > 1) + inv_push_step_indicator(st, sensor_ind, steps); + st->sensor[sensor_ind].ts += (u64)st->sensor[sensor_ind].dur; + t = st->sensor[sensor_ind].ts; + if (sensor_ind == SENSOR_COMPASS) { + if (!st->chip_config.normal_compass_measure) { + st->chip_config.normal_compass_measure = 1; + dptr += HEADERED_NORMAL_BYTES; + continue; + } + for (i = 0; i < 6; i++) + st->fifo_data[i] = dptr[i + 2]; + st->slave_compass->read_data(st, sen); + inv_push_8bytes_buffer(st, hdr | (!!steps), t, sen); + dptr += HEADERED_NORMAL_BYTES; + continue; + } + if (sensor_ind == SENSOR_PRESSURE) { + if (!st->chip_config.normal_pressure_measure) { + st->chip_config.normal_pressure_measure = 1; + dptr += HEADERED_NORMAL_BYTES; + continue; + } + for (i = 0; i < 6; i++) + st->fifo_data[i] = dptr[i + 2]; + st->slave_pressure->read_data(st, sen); + inv_push_8bytes_buffer(st, hdr | (!!steps), t, sen); + dptr += HEADERED_NORMAL_BYTES; + continue; + } + if (st->sensor[sensor_ind].sample_size == HEADERED_Q_BYTES) { + for (i = 0; i < 3; i++) + q[i] = (int)be32_to_cpup((__be32 *)(dptr + 4 + + i * 4)); + inv_push_16bytes_buffer(st, hdr | (!!steps), t, q); + } else { + for (i = 0; i < 3; i++) + sen[i] = (short)be16_to_cpup((__be16 *)(dptr + + 2 + i * 2)); + inv_push_8bytes_buffer(st, hdr | (!!steps), t, sen); + } + dptr += st->sensor[sensor_ind].sample_size; + } + inv_adjust_sensor_ts(st, sensor_ind); + st->left_over_size = target_bytes - (dptr - d); + + if (st->left_over_size) + memcpy(st->left_over, dptr, st->left_over_size); + + return 0; +} + +int inv_read_time_and_ticks(struct inv_mpu_state *st, bool resume) +{ + int result; + u32 counter; + u8 data[4]; + +#define MIN_TICK_READING_TIME NSEC_PER_SEC + st->last_ts = get_time_ns(); + if ((st->last_ts - st->prev_ts < MIN_TICK_READING_TIME) && + (!resume)) { + st->chip_config.adjust_time = false; + return 0; + } + result = mpu_memory_read(st, st->i2c_addr, + inv_dmp_get_address(KEY_DMP_RUN_CNTR), 4, data); + if (result) + return result; + + counter = be32_to_cpup((__be32 *)(data)); + if (resume) { + st->dmp_counter = counter; + st->prev_ts = st->last_ts; + + return 0; + } + if (counter > st->dmp_counter) + st->dmp_ticks = counter - st->dmp_counter; + else + st->dmp_ticks = 0xffffffff - st->dmp_counter + counter + 1; + st->dmp_counter = counter; + st->chip_config.adjust_time = true; + + return 0; +} + +/* + * inv_read_fifo() - Transfer data from FIFO to ring buffer. + */ +irqreturn_t inv_read_fifo(int irq, void *dev_id) +{ + + struct inv_mpu_state *st = (struct inv_mpu_state *)dev_id; + struct iio_dev *indio_dev = iio_priv_to_dev(st); + int result, bpm; + u8 data[MAX_HW_FIFO_BYTES]; + u16 fifo_count; + struct inv_reg_map_s *reg; + u64 pts1; + +#define DMP_MIN_RUN_TIME (37 * NSEC_PER_MSEC) + + mutex_lock(&indio_dev->mlock); + if (st->chip_config.dmp_on) { + pts1 = get_time_ns(); + result = inv_process_dmp_interrupt(st); + if (result || st->chip_config.dmp_event_int_on) + goto end_session; + if (!st->chip_config.smd_triggered) { + if (pts1 - st->last_run_time < DMP_MIN_RUN_TIME) + goto end_session; + else + st->last_run_time = pts1; + } else { + st->chip_config.smd_triggered = false; + } + } + + if (!(iio_buffer_enabled(indio_dev)) || (!st->chip_config.enable)) + goto end_session; + + reg = &st->reg; + if (!(st->sensor[SENSOR_ACCEL].on | + st->sensor[SENSOR_GYRO].on | + st->sensor[SENSOR_COMPASS].on | + st->sensor[SENSOR_PRESSURE].on | + st->chip_config.dmp_on | + st->mot_int.mot_on)) + goto end_session; + if (st->chip_config.lpa_mode) { + result = inv_i2c_read(st, reg->raw_accel, + BYTES_PER_SENSOR, data); + if (result) + goto end_session; + inv_report_gyro_accel(indio_dev, data, get_time_ns()); + if (st->mot_int.mot_on) + inv_process_motion(st); + + goto end_session; + } + + if (st->chip_config.dmp_on) { + result = inv_read_time_and_ticks(st, false); + if (result) + goto end_session; + } + bpm = st->chip_config.bytes_per_datum; + fifo_count = 0; + if (bpm) { + result = inv_i2c_read(st, reg->fifo_count_h, FIFO_COUNT_BYTE, + data); + if (result) + goto end_session; + fifo_count = be16_to_cpup((__be16 *)(data)); + /* fifo count can't be odd number */ + if (fifo_count & 1) + goto flush_fifo; + if (fifo_count == 0) + goto end_session; + st->fifo_count = fifo_count; + } + + if (st->batch.step_only) { + process_step_only_batch(st); + st->batch.post_isr_run = true; + } else if (st->chip_config.dmp_on) { + result = inv_process_batchmode(st, false); + } else { + if (fifo_count > FIFO_THRESHOLD) + goto flush_fifo; + if (bpm) { + while (fifo_count >= bpm) { + result = inv_i2c_read(st, reg->fifo_r_w, bpm, + data); + if (result) + goto flush_fifo; + result = inv_get_timestamp(st, + fifo_count / bpm); + if (result) + goto flush_fifo; + inv_report_gyro_accel(indio_dev, data, + st->last_ts); + fifo_count -= bpm; + } + } else { + result = inv_get_timestamp(st, 1); + if (result) + goto flush_fifo; + } + inv_send_compass_data(st); + inv_send_pressure_data(st); + } +end_session: + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; +flush_fifo: + /* Flush HW and SW FIFOs. */ + inv_reset_fifo(indio_dev); + inv_clear_kfifo(st); + mutex_unlock(&indio_dev->mlock); + + return IRQ_HANDLED; +} + +void inv_mpu_unconfigure_ring(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + free_irq(st->client->irq, st); + iio_kfifo_free(indio_dev->buffer); +}; + +static int inv_predisable(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + int result; + + if (st->chip_config.enable) { + result = set_inv_enable(indio_dev, false); + if (result) + return result; + result = st->set_power_state(st, false); + if (result) + return result; + } + + return 0; +} + +static int inv_check_conflict_sysfs(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + + if (st->chip_config.lpa_mode) { + /* dmp cannot run with low power mode on */ + st->chip_config.dmp_on = 0; + st->chip_config.gyro_enable = false; + st->sensor[SENSOR_GYRO].on = false; + st->sensor[SENSOR_COMPASS].on = false; + } + if (st->sensor[SENSOR_GYRO].on && + (!st->chip_config.gyro_enable)) { + st->chip_config.gyro_enable = true; + } + if (st->sensor[SENSOR_ACCEL].on && + (!st->chip_config.accel_enable)) { + st->chip_config.accel_enable = true; + } + + return 0; +} + +static int inv_preenable(struct iio_dev *indio_dev) +{ + int result; + + result = inv_check_conflict_sysfs(indio_dev); + if (result) + return result; + result = iio_sw_buffer_preenable(indio_dev); + + return result; +} + +void inv_init_sensor_struct(struct inv_mpu_state *st) +{ + int i; + + for (i = 0; i < SENSOR_NUM_MAX; i++) { + if (i < SENSOR_SIXQ) + st->sensor[i].sample_size = + HEADERED_NORMAL_BYTES; + else + st->sensor[i].sample_size = HEADERED_Q_BYTES; + if (i == SENSOR_STEP) { + st->sensor[i].rate = 1; + st->sensor[i].dur = NSEC_PER_SEC; + } else { + st->sensor[i].rate = INIT_DMP_OUTPUT_RATE; + st->sensor[i].dur = NSEC_PER_SEC / + INIT_DMP_OUTPUT_RATE; + } + } + + st->sensor[SENSOR_ACCEL].send_data = inv_send_accel_data; + st->sensor[SENSOR_GYRO].send_data = inv_send_gyro_data; + st->sensor[SENSOR_COMPASS].send_data = inv_send_compass_dmp_data; + st->sensor[SENSOR_PRESSURE].send_data = inv_send_pressure_dmp_data; + st->sensor[SENSOR_STEP].send_data = inv_send_step_detector; + st->sensor[SENSOR_PEDQ].send_data = inv_send_ped_q_data; + st->sensor[SENSOR_SIXQ].send_data = inv_send_six_q_data; + st->sensor[SENSOR_LPQ].send_data = inv_send_three_q_data; + + st->sensor[SENSOR_ACCEL].set_rate = inv_set_accel_rate; + st->sensor[SENSOR_GYRO].set_rate = inv_set_gyro_rate; + st->sensor[SENSOR_COMPASS].set_rate = inv_set_compass_rate; + st->sensor[SENSOR_PRESSURE].set_rate = inv_set_pressure_rate; + st->sensor[SENSOR_STEP].set_rate = inv_set_step_detector; + st->sensor[SENSOR_PEDQ].set_rate = inv_set_pedq_rate; + st->sensor[SENSOR_SIXQ].set_rate = inv_set_sixq_rate; + st->sensor[SENSOR_LPQ].set_rate = inv_set_lpq_rate; +} + +void batch_step_only_work(struct work_struct *work) +{ + struct inv_mpu_state *st = container_of((struct delayed_work *)work, + struct inv_mpu_state, work); + struct iio_dev *indio_dev = iio_priv_to_dev(st); + u32 delay = msecs_to_jiffies(st->batch.timeout); + u8 data[FIFO_COUNT_BYTE]; + int result; + + mutex_lock(&indio_dev->mlock); + if (!(iio_buffer_enabled(indio_dev)) || (!st->chip_config.enable)) + goto error_ret; + schedule_delayed_work(&st->work, delay); + + if (st->batch.post_isr_run) { + st->batch.post_isr_run = false; + } else { + result = inv_i2c_read(st, st->reg.fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + goto error_ret; + st->fifo_count = be16_to_cpup((__be16 *)(data)); + process_step_only_batch(st); + } + +error_ret: + mutex_unlock(&indio_dev->mlock); + +} + +int inv_flush_batch_data(struct iio_dev *indio_dev, bool *has_data) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + struct inv_reg_map_s *reg; + u8 data[2]; + int result; + + reg = &st->reg; + if (!(iio_buffer_enabled(indio_dev)) || (!st->chip_config.enable)) + return -EINVAL; + + if (st->batch.on) { + result = inv_read_time_and_ticks(st, false); + if (result) + return result; + result = inv_i2c_read(st, reg->fifo_count_h, + FIFO_COUNT_BYTE, data); + if (result) + return result; + st->fifo_count = be16_to_cpup((__be16 *)(data)); + result = inv_process_batchmode(st, true); + if (result) + return result; + *has_data = !!st->fifo_count; + } else { + return -EINVAL; + } + + return 0; +} + +static const struct iio_buffer_setup_ops inv_mpu_ring_setup_ops = { + .preenable = &inv_preenable, + .predisable = &inv_predisable, +}; + +int inv_mpu_configure_ring(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu_state *st = iio_priv(indio_dev); + struct iio_buffer *ring; + + ring = iio_kfifo_allocate(indio_dev); + if (!ring) + return -ENOMEM; + indio_dev->buffer = ring; + /* setup ring buffer */ + ring->scan_timestamp = true; + indio_dev->setup_ops = &inv_mpu_ring_setup_ops; + /*scan count double count timestamp. should subtract 1. but + number of channels still includes timestamp*/ + if (INV_MPU3050 == st->chip_type) + ret = request_threaded_irq(st->client->irq, inv_irq_handler, + inv_read_fifo_mpu3050, + IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st); + else + ret = request_threaded_irq(st->client->irq, inv_irq_handler, + inv_read_fifo, + IRQF_TRIGGER_RISING | IRQF_SHARED, "inv_irq", st); + if (ret) + goto error_iio_sw_rb_free; + + indio_dev->modes |= INDIO_BUFFER_TRIGGERED; + + return 0; +error_iio_sw_rb_free: + iio_kfifo_free(indio_dev->buffer); + + return ret; +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_mpu_trigger.c b/drivers/iio/imu/inv_mpu9250/inv_mpu_trigger.c new file mode 100644 index 0000000000000..88b71ae4da11d --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_mpu_trigger.c @@ -0,0 +1,80 @@ +/* +* 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 +#include +#include +#include +#include +#include +#include +#include + +#include "iio.h" +#include "sysfs.h" +#include "trigger.h" + +#include "inv_mpu_iio.h" + +/* + * inv_mpu_data_rdy_trigger_set_state() set data ready interrupt state + */ +static int inv_mpu_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + return 0; +} + +static const struct iio_trigger_ops inv_mpu_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &inv_mpu_data_rdy_trigger_set_state, +}; + +int inv_mpu_probe_trigger(struct iio_dev *indio_dev) +{ + int ret; + struct inv_mpu_state *st = iio_priv(indio_dev); + + st->trig = iio_allocate_trigger("%s-dev%d", + indio_dev->name, + indio_dev->id); + if (st->trig == NULL) + return -ENOMEM; + st->trig->dev.parent = &st->client->dev; + st->trig->private_data = indio_dev; + st->trig->ops = &inv_mpu_trigger_ops; + ret = iio_trigger_register(st->trig); + + if (ret) { + iio_free_trigger(st->trig); + return -EPERM; + } + indio_dev->trig = st->trig; + + return 0; +} + +void inv_mpu_remove_trigger(struct iio_dev *indio_dev) +{ + struct inv_mpu_state *st = iio_priv(indio_dev); + + iio_trigger_unregister(st->trig); + iio_free_trigger(st->trig); +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_slave_bma250.c b/drivers/iio/imu/inv_mpu9250/inv_slave_bma250.c new file mode 100644 index 0000000000000..99ae702e67ad2 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_slave_bma250.c @@ -0,0 +1,315 @@ +/* +* 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 +#include +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" +#define BMA250_CHIP_ID 3 +#define BMA250_RANGE_SET 0 +#define BMA250_BW_SET 4 + +/* range and bandwidth */ +#define BMA250_RANGE_2G 3 +#define BMA250_RANGE_4G 5 +#define BMA250_RANGE_8G 8 +#define BMA250_RANGE_16G 12 +#define BMA250_RANGE_MAX 4 +#define BMA250_RANGE_MASK 0xF0 + +#define BMA250_BW_7_81HZ 0x08 +#define BMA250_BW_15_63HZ 0x09 +#define BMA250_BW_31_25HZ 0x0A +#define BMA250_BW_62_50HZ 0x0B +#define BMA250_BW_125HZ 0x0C +#define BMA250_BW_250HZ 0x0D +#define BMA250_BW_500HZ 0x0E +#define BMA250_BW_1000HZ 0x0F +#define BMA250_MAX_BW_SIZE 8 +#define BMA250_BW_REG_MASK 0xE0 + +/* register definitions */ +#define BMA250_X_AXIS_LSB_REG 0x02 +#define BMA250_RANGE_SEL_REG 0x0F +#define BMA250_BW_SEL_REG 0x10 +#define BMA250_MODE_CTRL_REG 0x11 + +/* mode settings */ +#define BMA250_MODE_NORMAL 0 +#define BMA250_MODE_LOWPOWER 1 +#define BMA250_MODE_SUSPEND 2 +#define BMA250_MODE_MAX 3 +#define BMA250_MODE_MASK 0x3F +#define BMA250_BIT_SUSPEND 0x80 +#define BMA250_BIT_LP 0x40 + +struct bma_property { + int range; + int bandwidth; + int mode; +}; + +static struct bma_property bma_static_property = { + .range = BMA250_RANGE_SET, + .bandwidth = BMA250_BW_SET, + .mode = BMA250_MODE_SUSPEND +}; + +static int bma250_set_bandwidth(struct inv_mpu_state *st, u8 bw) +{ + int res; + u8 data; + int bandwidth; + switch (bw) { + case 0: + bandwidth = BMA250_BW_7_81HZ; + break; + case 1: + bandwidth = BMA250_BW_15_63HZ; + break; + case 2: + bandwidth = BMA250_BW_31_25HZ; + break; + case 3: + bandwidth = BMA250_BW_62_50HZ; + break; + case 4: + bandwidth = BMA250_BW_125HZ; + break; + case 5: + bandwidth = BMA250_BW_250HZ; + break; + case 6: + bandwidth = BMA250_BW_500HZ; + break; + case 7: + bandwidth = BMA250_BW_1000HZ; + break; + default: + return -EINVAL; + } + res = inv_secondary_read(BMA250_BW_SEL_REG, 1, &data); + if (res) + return res; + data &= BMA250_BW_REG_MASK; + data |= bandwidth; + res = inv_secondary_write(BMA250_BW_SEL_REG, data); + return res; +} + +static int bma250_set_range(struct inv_mpu_state *st, u8 range) +{ + int res; + u8 orig, data; + switch (range) { + case 0: + data = BMA250_RANGE_2G; + break; + case 1: + data = BMA250_RANGE_4G; + break; + case 2: + data = BMA250_RANGE_8G; + break; + case 3: + data = BMA250_RANGE_16G; + break; + default: + return -EINVAL; + } + res = inv_secondary_read(BMA250_RANGE_SEL_REG, 1, &orig); + if (res) + return res; + orig &= BMA250_RANGE_MASK; + data |= orig; + res = inv_secondary_write(BMA250_RANGE_SEL_REG, data); + if (res) + return res; + bma_static_property.range = range; + + return 0; +} + +static int setup_slave_bma250(struct inv_mpu_state *st) +{ + int result; + u8 data[2]; + result = set_3050_bypass(st, true); + if (result) + return result; + /*read secondary i2c ID register */ + result = inv_secondary_read(0, 1, data); + if (result) + return result; + if (BMA250_CHIP_ID != data[0]) + return -EINVAL; + result = set_3050_bypass(st, false); + if (result) + return result; + /*AUX(accel), slave address is set inside set_3050_bypass*/ + /* bma250 x axis LSB register address is 2 */ + result = inv_i2c_single_write(st, REG_3050_AUX_BST_ADDR, + BMA250_X_AXIS_LSB_REG); + + return result; +} + +static int bma250_set_mode(struct inv_mpu_state *st, u8 mode) +{ + int res; + u8 data; + + res = inv_secondary_read(BMA250_MODE_CTRL_REG, 1, &data); + if (res) + return res; + data &= BMA250_MODE_MASK; + switch (mode) { + case BMA250_MODE_NORMAL: + break; + case BMA250_MODE_LOWPOWER: + data |= BMA250_BIT_LP; + break; + case BMA250_MODE_SUSPEND: + data |= BMA250_BIT_SUSPEND; + break; + default: + return -EINVAL; + } + res = inv_secondary_write(BMA250_MODE_CTRL_REG, data); + if (res) + return res; + bma_static_property.mode = mode; + + return 0; +} + +static int suspend_slave_bma250(struct inv_mpu_state *st) +{ + int result; + if (bma_static_property.mode == BMA250_MODE_SUSPEND) + return 0; + /*set to bypass mode */ + result = set_3050_bypass(st, true); + if (result) + return result; + bma250_set_mode(st, BMA250_MODE_SUSPEND); + /* no need to recover to non-bypass mode because we need it now */ + + return 0; +} + +static int resume_slave_bma250(struct inv_mpu_state *st) +{ + int result; + if (bma_static_property.mode == BMA250_MODE_NORMAL) + return 0; + /*set to bypass mode */ + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_mode(st, BMA250_MODE_NORMAL); + /* recover bypass mode */ + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} + +static int combine_data_slave_bma250(u8 *in, short *out) +{ + out[0] = le16_to_cpup((__le16 *)(&in[0])); + out[1] = le16_to_cpup((__le16 *)(&in[2])); + out[2] = le16_to_cpup((__le16 *)(&in[4])); + + return 0; +} + +static int get_mode_slave_bma250(void) +{ + switch (bma_static_property.mode) { + case BMA250_MODE_SUSPEND: + return INV_MODE_SUSPEND; + case BMA250_MODE_NORMAL: + return INV_MODE_NORMAL; + default: + return -EINVAL; + } +} + +/** + * set_lpf_bma250() - set lpf value + */ + +static int set_lpf_bma250(struct inv_mpu_state *st, int rate) +{ + const short hz[] = {1000, 500, 250, 125, 62, 31, 15, 7}; + const int d[] = {7, 6, 5, 4, 3, 2, 1, 0}; + int i, h, data, result; + h = (rate >> 1); + i = 0; + while ((h < hz[i]) && (i < ARRAY_SIZE(hz) - 1)) + i++; + data = d[i]; + + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_bandwidth(st, (u8) data); + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} +/** + * set_fs_bma250() - set range value + */ + +static int set_fs_bma250(struct inv_mpu_state *st, int fs) +{ + int result; + result = set_3050_bypass(st, true); + if (result) + return result; + result = bma250_set_range(st, (u8) fs); + result |= set_3050_bypass(st, false); + + return result ? (-EINVAL) : 0; +} + +static struct inv_mpu_slave slave_bma250 = { + .suspend = suspend_slave_bma250, + .resume = resume_slave_bma250, + .setup = setup_slave_bma250, + .combine_data = combine_data_slave_bma250, + .get_mode = get_mode_slave_bma250, + .set_lpf = set_lpf_bma250, + .set_fs = set_fs_bma250 +}; + +int inv_register_mpu3050_slave(struct inv_mpu_state *st) +{ + st->slave_accel = &slave_bma250; + + return 0; +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_slave_compass.c b/drivers/iio/imu/inv_mpu9250/inv_slave_compass.c new file mode 100644 index 0000000000000..de5014c108e8b --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_slave_compass.c @@ -0,0 +1,804 @@ +/* +* 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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" +/* AKM definitions */ +#define REG_AKM_ID 0x00 +#define REG_AKM_INFO 0x01 +#define REG_AKM_STATUS 0x02 +#define REG_AKM_MEASURE_DATA 0x03 +#define REG_AKM_MODE 0x0A +#define REG_AKM_ST_CTRL 0x0C +#define REG_AKM_SENSITIVITY 0x10 +#define REG_AKM8963_CNTL1 0x0A + +#define DATA_AKM_ID 0x48 +#define DATA_AKM_MODE_PD 0x00 +#define DATA_AKM_MODE_SM 0x01 +#define DATA_AKM_MODE_ST 0x08 +#define DATA_AKM_MODE_FR 0x0F +#define DATA_AKM_SELF_TEST 0x40 +#define DATA_AKM_DRDY 0x01 +#define DATA_AKM8963_BIT 0x10 +#define DATA_AKM_STAT_MASK 0x0C + +#define DATA_AKM8975_SCALE (9830 * (1L << 15)) +#define DATA_AKM8972_SCALE (19661 * (1L << 15)) +#define DATA_AKM8963_SCALE0 (19661 * (1L << 15)) +#define DATA_AKM8963_SCALE1 (4915 * (1L << 15)) +#define DATA_MLX_SCALE (4915 * (1L << 15)) +#define DATA_MLX_SCALE_EMPIRICAL (26214 * (1L << 15)) + +#define DATA_AKM8963_SCALE_SHIFT 4 +#define DATA_AKM_BYTES_DMP 10 +#define DATA_AKM_BYTES 8 +#define DATA_AKM_MIN_READ_TIME (9 * NSEC_PER_MSEC) + +#define DEF_ST_COMPASS_WAIT_MIN (10 * 1000) +#define DEF_ST_COMPASS_WAIT_MAX (15 * 1000) +#define DEF_ST_COMPASS_TRY_TIMES 10 +#define DEF_ST_COMPASS_8963_SHIFT 2 +#define X 0 +#define Y 1 +#define Z 2 + +/* milliseconds between each access */ +#define AKM_RATE_SCALE 10 +#define MLX_RATE_SCALE 50 + +/* MLX90399 compass definition */ +#define DATA_MLX_CMD_READ_MEASURE 0x4F +#define DATA_MLX_CMD_SINGLE_MEASURE 0x3F +#define DATA_MLX_READ_DATA_BYTES 9 +#define DATA_MLX_STATUS_DATA 3 +#define DATA_MLX_MIN_READ_TIME (95 * NSEC_PER_MSEC) + +static const short AKM8975_ST_Lower[3] = {-100, -100, -1000}; +static const short AKM8975_ST_Upper[3] = {100, 100, -300}; + +static const short AKM8972_ST_Lower[3] = {-50, -50, -500}; +static const short AKM8972_ST_Upper[3] = {50, 50, -100}; + +static const short AKM8963_ST_Lower[3] = {-200, -200, -3200}; +static const short AKM8963_ST_Upper[3] = {200, 200, -800}; + +/* + * inv_setup_compass_akm() - Configure akm series compass. + */ +static int inv_setup_compass_akm(struct inv_mpu_state *st) +{ + int result; + u8 data[4]; + + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) + return result; + /*read secondary i2c ID register */ + result = inv_secondary_read(REG_AKM_ID, 1, data); + if (result) + return result; + if (data[0] != DATA_AKM_ID) + return -ENXIO; + /*set AKM to Fuse ROM access mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_FR); + if (result) + return result; + result = inv_secondary_read(REG_AKM_SENSITIVITY, THREE_AXIS, + st->chip_info.compass_sens); + if (result) + return result; + /*revert to power down mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + if (result) + return result; + pr_debug("%s senx=%d, seny=%d, senz=%d\n", + st->hw->name, + st->chip_info.compass_sens[0], + st->chip_info.compass_sens[1], + st->chip_info.compass_sens[2]); + /*restore to non-bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + if (result) + return result; + + /*setup master mode and master clock and ES bit*/ + result = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES); + if (result) + return result; + /*slave 1 is used for AKM mode change only*/ + result = inv_i2c_single_write(st, REG_I2C_SLV1_ADDR, + st->plat_data.secondary_i2c_addr); + if (result) + return result; + /* AKM mode register address is 0x0A */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_REG, REG_AKM_MODE); + if (result) + return result; + /* output data for slave 1 is fixed, single measure mode*/ + st->slave_compass->scale = 1; + if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) { + st->slave_compass->st_upper = AKM8975_ST_Upper; + st->slave_compass->st_lower = AKM8975_ST_Lower; + data[0] = DATA_AKM_MODE_SM; + } else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) { + st->slave_compass->st_upper = AKM8972_ST_Upper; + st->slave_compass->st_lower = AKM8972_ST_Lower; + data[0] = DATA_AKM_MODE_SM; + } else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) { + st->slave_compass->st_upper = AKM8963_ST_Upper; + st->slave_compass->st_lower = AKM8963_ST_Lower; + data[0] = DATA_AKM_MODE_SM | + (st->slave_compass->scale << DATA_AKM8963_SCALE_SHIFT); + } + result = inv_i2c_single_write(st, INV_MPU_REG_I2C_SLV1_DO, data[0]); + + return result; +} + +static int inv_akm_read_data(struct inv_mpu_state *st, short *o) +{ + int result; + int i; + u8 d[DATA_AKM_BYTES]; + u8 *sens; + + sens = st->chip_info.compass_sens; + if (st->chip_config.dmp_on) { + for (i = 0; i < 6; i++) + d[1 + i] = st->fifo_data[i]; + } else { + result = inv_i2c_read(st, REG_EXT_SENS_DATA_00, + DATA_AKM_BYTES, d); + if ((DATA_AKM_DRDY != d[0]) || result) + for (i = 0; i < 6; i++) + d[1 + i] = 0; + } + for (i = 0; i < 3; i++) { + o[i] = (short)((d[i * 2 + 2] << 8) | d[i * 2 + 1]); + o[i] = (short)(((int)o[i] * (sens[i] + 128)) >> 8); + } + + return 0; +} + +static int inv_mlx_read_data(struct inv_mpu_state *st, short *o) +{ + int result; + int i, z; + u8 d[DATA_MLX_READ_DATA_BYTES]; + + result = inv_i2c_read(st, REG_EXT_SENS_DATA_00, + DATA_MLX_READ_DATA_BYTES, d); + if ((!(d[0] & ~DATA_MLX_STATUS_DATA)) && (!result)) { + for (i = 0; i < 3; i++) + o[i] = (short)((d[i * 2 + 3] << 8) + d[i * 2 + 4]); + } else { + for (i = 0; i < 3; i++) + o[i] = 0; + } + z = o[2]; + /* axis sensitivity conversion. Z axis has different sensitiviy from + x and y */ + z *= 26; + z /= 15; + o[2] = z; + + return 0; +} + +static int inv_check_akm_self_test(struct inv_mpu_state *st) +{ + int result; + u8 data[6]; + u8 counter, cntl; + short x, y, z; + u8 *sens; + sens = st->chip_info.compass_sens; + + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) { + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + return result; + } + /* set to power down mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + if (result) + goto AKM_fail; + + /* write 1 to ASTC register */ + result = inv_secondary_write(REG_AKM_ST_CTRL, DATA_AKM_SELF_TEST); + if (result) + goto AKM_fail; + /* set self test mode */ + result = inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_ST); + if (result) + goto AKM_fail; + counter = DEF_ST_COMPASS_TRY_TIMES; + while (counter > 0) { + usleep_range(DEF_ST_COMPASS_WAIT_MIN, DEF_ST_COMPASS_WAIT_MAX); + result = inv_secondary_read(REG_AKM_STATUS, 1, data); + if (result) + goto AKM_fail; + if ((data[0] & DATA_AKM_DRDY) == 0) + counter--; + else + counter = 0; + } + if ((data[0] & DATA_AKM_DRDY) == 0) { + result = -EINVAL; + goto AKM_fail; + } + result = inv_secondary_read(REG_AKM_MEASURE_DATA, + BYTES_PER_SENSOR, data); + if (result) + goto AKM_fail; + + x = le16_to_cpup((__le16 *)(&data[0])); + y = le16_to_cpup((__le16 *)(&data[2])); + z = le16_to_cpup((__le16 *)(&data[4])); + x = ((x * (sens[0] + 128)) >> 8); + y = ((y * (sens[1] + 128)) >> 8); + z = ((z * (sens[2] + 128)) >> 8); + if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) { + result = inv_secondary_read(REG_AKM8963_CNTL1, 1, &cntl); + if (result) + goto AKM_fail; + if (0 == (cntl & DATA_AKM8963_BIT)) { + x <<= DEF_ST_COMPASS_8963_SHIFT; + y <<= DEF_ST_COMPASS_8963_SHIFT; + z <<= DEF_ST_COMPASS_8963_SHIFT; + } + } + result = -EINVAL; + if (x > st->slave_compass->st_upper[X] || + x < st->slave_compass->st_lower[X]) + goto AKM_fail; + if (y > st->slave_compass->st_upper[Y] || + y < st->slave_compass->st_lower[Y]) + goto AKM_fail; + if (z > st->slave_compass->st_upper[Z] || + z < st->slave_compass->st_lower[Z]) + goto AKM_fail; + result = 0; +AKM_fail: + /*write 0 to ASTC register */ + result |= inv_secondary_write(REG_AKM_ST_CTRL, 0); + /*set to power down mode */ + result |= inv_secondary_write(REG_AKM_MODE, DATA_AKM_MODE_PD); + /*restore to non-bypass mode */ + result |= inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + return result; +} + +/* + * inv_write_akm_scale() - Configure the akm scale range. + */ +static int inv_write_akm_scale(struct inv_mpu_state *st, int data) +{ + char d, en; + int result; + + if (COMPASS_ID_AK8963 != st->plat_data.sec_slave_id) + return 0; + en = !!data; + if (st->slave_compass->scale == en) + return 0; + d = (DATA_AKM_MODE_SM | (en << DATA_AKM8963_SCALE_SHIFT)); + result = inv_i2c_single_write(st, INV_MPU_REG_I2C_SLV1_DO, d); + if (result) + return result; + st->slave_compass->scale = en; + + return 0; +} + +/* + * inv_read_akm_scale() - show AKM scale. + */ +static int inv_read_akm_scale(struct inv_mpu_state *st, int *scale) +{ + if (COMPASS_ID_AK8975 == st->plat_data.sec_slave_id) + *scale = DATA_AKM8975_SCALE; + else if (COMPASS_ID_AK8972 == st->plat_data.sec_slave_id) + *scale = DATA_AKM8972_SCALE; + else if (COMPASS_ID_AK8963 == st->plat_data.sec_slave_id) + if (st->slave_compass->scale) + *scale = DATA_AKM8963_SCALE1; + else + *scale = DATA_AKM8963_SCALE0; + else + return -EINVAL; + + return IIO_VAL_INT; +} + +static int inv_suspend_akm(struct inv_mpu_state *st) +{ + int result; + + /* slave 0 is disabled */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, 0); + if (result) + return result; + /* slave 1 is disabled */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, 0); + + return result; +} + +static int inv_resume_akm(struct inv_mpu_state *st) +{ + int result; + u8 reg_addr, bytes; + + /* slave 0 is used to read data from compass */ + /*read mode */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, + INV_MPU_BIT_I2C_READ | + st->plat_data.secondary_i2c_addr); + if (result) + return result; + /* AKM status register address is 1 */ + if (st->chip_config.dmp_on) { + reg_addr = REG_AKM_INFO; + bytes = DATA_AKM_BYTES_DMP; + } else { + reg_addr = REG_AKM_STATUS; + bytes = DATA_AKM_BYTES; + } + result = inv_i2c_single_write(st, REG_I2C_SLV0_REG, reg_addr); + if (result) + return result; + + /* slave 0 is enabled, read 10 or 8 bytes from here */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, + INV_MPU_BIT_SLV_EN | bytes); + if (result) + return result; + /* slave 1 is enabled, write byte length is 1 */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, + INV_MPU_BIT_SLV_EN | 1); + + return result; +} + +/* + * inv_write_mlx_scale() - Configure the mlx90399 scale range. + */ +static int inv_write_mlx_scale(struct inv_mpu_state *st, int data) +{ + st->slave_compass->scale = data; + return 0; +} + +/* + * inv_read_mlx_scale() - show mlx90399 scale. + */ +static int inv_read_mlx_scale(struct inv_mpu_state *st, int *scale) +{ + *scale = st->slave_compass->scale; + return IIO_VAL_INT; +} + +static int inv_i2c_read_mlx(struct inv_mpu_state *st, u16 i2c_addr, + u16 length, u8 *data) +{ + struct i2c_msg msgs[1]; + int res; + + if (!data) + return -EINVAL; + + msgs[0].addr = i2c_addr; + msgs[0].flags = I2C_M_RD; + msgs[0].buf = data; + msgs[0].len = length; + + res = i2c_transfer(st->sl_handle, msgs, 1); + + if (res < 1) { + if (res >= 0) + res = -EIO; + } else + res = 0; + + return res; +} + +static int inv_i2c_write_mlx(struct inv_mpu_state *st, + u16 i2c_addr, u8 data) +{ + u8 tmp[1]; + struct i2c_msg msg; + int res; + + tmp[0] = data; + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 1; + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } else + return 0; +} + +static int inv_i2c_read_reg_mlx(struct inv_mpu_state *st, + u16 i2c_addr, u8 reg, u16 *val) +{ + u8 tmp[10]; + struct i2c_msg msg; + int res; + + tmp[0] = 0x50; + tmp[1] = (reg << 2); + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 2; + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } + res = inv_i2c_read_mlx(st, i2c_addr, 10, tmp); + if (res) + return res; + *val = ((tmp[1] << 8) | tmp[2]); + + return res; +} + +static int inv_i2c_write_mlx_reg(struct inv_mpu_state *st, + u16 i2c_addr, int reg, u16 d) +{ + u8 tmp[10]; + struct i2c_msg msg; + int res; + + /* write register command, writing volatile memory */ + tmp[0] = 0x60; + tmp[1] = ((d >> 8) & 0xff); + tmp[2] = (d & 0xff); + tmp[3] = (reg << 2); + msg.addr = i2c_addr; + msg.flags = 0; /* write */ + msg.buf = tmp; + msg.len = 4; + + res = i2c_transfer(st->sl_handle, &msg, 1); + if (res < 1) { + if (res == 0) + res = -EIO; + return res; + } + /* read status */ + res = inv_i2c_read_mlx(st, i2c_addr, 10, tmp); + + return res; +} + +static int inv_write_mlx_cmd(struct inv_mpu_state *st, u8 cmd) +{ + int result; + u8 d[10]; + int addr; + + addr = st->plat_data.secondary_i2c_addr; + result = inv_i2c_write_mlx(st, addr, cmd); + if (result) + return result; + /* read back status byte */ + result = inv_i2c_read_mlx(st, addr, 10, d); + + return result; +} + +static int inv_read_mlx_z_axis(struct inv_mpu_state *st, s16 *z) +{ + int result; + u8 d[10]; + int addr; + + addr = st->plat_data.secondary_i2c_addr; + + /* measure z axis */ + result = inv_write_mlx_cmd(st, 0x39); + if (result) + return result; + msleep(100); + /* read z axis */ + result = inv_i2c_write_mlx(st, addr, 0x49); + if (result) + return result; + /* read back status byte */ + result = inv_i2c_read_mlx(st, addr, 10, d); + if (result) + return result; + if ((d[0] & 0x3) == 1) + *z = (short)((d[3] << 8) + d[4]); + else + return -EINVAL; + + return 0; +} + +static int inv_write_mlx_reg(struct inv_mpu_state *st) +{ + int result; + int addr; + u16 r_val; + + addr = st->plat_data.secondary_i2c_addr; + + /* write register 0. + set GAIN_SEL as 7; + set HALL_CONF as 0xC. */ + result = inv_i2c_write_mlx_reg(st, addr, 0, 0x7c); + if (result) + return result; + /* write register 2. + set resolution is zero for all axes; + set DIGI filter as 6. + set OSR as 0. + set OSR2 as 0. */ + result = inv_i2c_write_mlx_reg(st, addr, 2, 0x18); + if (result) + return result; + /* read register 1 */ + result = inv_i2c_read_reg_mlx(st, addr, 1, &r_val); + if (result) + return result; + /* enable temp comp */ + r_val |= 0x400; + result = inv_i2c_write_mlx_reg(st, addr, 1, r_val); + /* the value should be kept in the volatile memory */ + + return result; +} + +static int inv_check_mlx_self_test(struct inv_mpu_state *st) +{ + int result; + int addr; + s16 meas_ref, meas_coil; + u16 diff, r_val; + + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) { + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + return result; + } + + addr = st->plat_data.secondary_i2c_addr; + + /* fake read to flush the previous data */ + result = inv_read_mlx_z_axis(st, &meas_ref); + + result = inv_read_mlx_z_axis(st, &meas_ref); + if (result) + return result; + + /* read register 1 */ + result = inv_i2c_read_reg_mlx(st, addr, 0, &r_val); + if (result) + return result; + /* enable self test */ + r_val |= 0x100; + result = inv_i2c_write_mlx_reg(st, addr, 0, r_val); + if (result) + return result; + msleep(200); + result = inv_read_mlx_z_axis(st, &meas_coil); + if (result) + return result; + result = inv_write_mlx_cmd(st, 0xD0); + if (result) + return result; + result = inv_write_mlx_reg(st); + if (result) + return result; + diff = abs(meas_ref - meas_coil); + if (diff < 25 || diff > 300) + result = 1; + + /*restore to non-bypass mode */ + result |= inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + + return result; +} + +/* + * inv_setup_compass_mlx() - Configure akm series compass. + */ +static int inv_setup_compass_mlx(struct inv_mpu_state *st) +{ + int result; + int addr; + + addr = st->plat_data.secondary_i2c_addr; + /* set to bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (result) + return result; + result = inv_write_mlx_reg(st); + if (result) + return result; + + /*restore to non-bypass mode */ + result = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + if (result) + return result; + + /*setup master mode and master clock and ES bit*/ + result = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES); + if (result) + return result; + + /* slave 0 used to write read measurement command, write mode */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, addr); + if (result) + return result; + /* ignore the register address, send out data only */ + result = inv_i2c_single_write(st, INV_MPU_REG_I2C_SLV0_DO, + DATA_MLX_CMD_READ_MEASURE); + if (result) + return result; + + /* slave 1 used to read status bytes and data of read measurement */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_ADDR, + INV_MPU_BIT_I2C_READ | addr); + if (result) + return result; + /* slave 2 used to write single measurement command, write mode */ + result = inv_i2c_single_write(st, REG_I2C_SLV2_ADDR, addr); + if (result) + return result; + /* ignore the register address, send out data only */ + result = inv_i2c_single_write(st, INV_MPU_REG_I2C_SLV2_DO, + DATA_MLX_CMD_SINGLE_MEASURE); + if (result) + return result; + /* slave 3 used to read status bytes and data of read measurement */ + result = inv_i2c_single_write(st, REG_I2C_SLV3_ADDR, + INV_MPU_BIT_I2C_READ | addr); + + st->slave_compass->scale = DATA_MLX_SCALE; + + return result; +} + +static int inv_suspend_mlx(struct inv_mpu_state *st) +{ + int result; + + result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, 0); + if (result) + return result; + result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, 0); + if (result) + return result; + result = inv_i2c_single_write(st, REG_I2C_SLV2_CTRL, 0); + if (result) + return result; + result = inv_i2c_single_write(st, REG_I2C_SLV3_CTRL, 0); + + return result; +} + +static int inv_resume_mlx(struct inv_mpu_state *st) +{ + int result; + + /* enable, ignore register, write 1 bytes */ + result = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, + INV_MPU_BIT_SLV_EN | + INV_MPU_BIT_REG_DIS | + 1); + if (result) + return result; + + /* enable, ignore register, read 9 bytes */ + result = inv_i2c_single_write(st, REG_I2C_SLV1_CTRL, + INV_MPU_BIT_SLV_EN | + INV_MPU_BIT_REG_DIS | + DATA_MLX_READ_DATA_BYTES); + if (result) + return result; + /* enable, ignore register, write 1 bytes */ + result = inv_i2c_single_write(st, REG_I2C_SLV2_CTRL, + INV_MPU_BIT_SLV_EN | + INV_MPU_BIT_REG_DIS | + 1); + if (result) + return result; + + /* enable, ignore register, read 1 bytes */ + result = inv_i2c_single_write(st, REG_I2C_SLV3_CTRL, + INV_MPU_BIT_SLV_EN | + INV_MPU_BIT_REG_DIS | + 1); + + return result; +} + +static struct inv_mpu_slave slave_akm = { + .suspend = inv_suspend_akm, + .resume = inv_resume_akm, + .get_scale = inv_read_akm_scale, + .set_scale = inv_write_akm_scale, + .self_test = inv_check_akm_self_test, + .setup = inv_setup_compass_akm, + .read_data = inv_akm_read_data, + .rate_scale = AKM_RATE_SCALE, + .min_read_time = DATA_AKM_MIN_READ_TIME, +}; + +static struct inv_mpu_slave slave_mlx90399 = { + .suspend = inv_suspend_mlx, + .resume = inv_resume_mlx, + .get_scale = inv_read_mlx_scale, + .set_scale = inv_write_mlx_scale, + .self_test = inv_check_mlx_self_test, + .setup = inv_setup_compass_mlx, + .read_data = inv_mlx_read_data, + .rate_scale = MLX_RATE_SCALE, + .min_read_time = DATA_MLX_MIN_READ_TIME, +}; + +int inv_mpu_setup_compass_slave(struct inv_mpu_state *st) +{ + switch (st->plat_data.sec_slave_id) { + case COMPASS_ID_AK8975: + case COMPASS_ID_AK8972: + case COMPASS_ID_AK8963: + st->slave_compass = &slave_akm; + break; + case COMPASS_ID_MLX90399: + st->slave_compass = &slave_mlx90399; + break; + default: + return -EINVAL; + } + + return st->slave_compass->setup(st); +} + diff --git a/drivers/iio/imu/inv_mpu9250/inv_slave_pressure.c b/drivers/iio/imu/inv_mpu9250/inv_slave_pressure.c new file mode 100644 index 0000000000000..ae45ad2ffc628 --- /dev/null +++ b/drivers/iio/imu/inv_mpu9250/inv_slave_pressure.c @@ -0,0 +1,510 @@ +/* +* 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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "inv_mpu_iio.h" + +/* Constants */ +#define SHIFT_RIGHT_4_POSITION 4 +#define SHIFT_LEFT_2_POSITION 2 +#define SHIFT_LEFT_4_POSITION 4 +#define SHIFT_LEFT_5_POSITION 5 +#define SHIFT_LEFT_8_POSITION 8 +#define SHIFT_LEFT_12_POSITION 12 +#define SHIFT_LEFT_16_POSITION 16 + +/* Sensor Specific constants */ +#define BMP280_SLEEP_MODE 0x00 +#define BMP280_FORCED_MODE 0x01 +#define BMP280_NORMAL_MODE 0x03 +#define BMP280_SOFT_RESET 0xB6 + +#define BMP280_DELAYTIME_MS_NONE 0 +#define BMP280_DELAYTIME_MS_5 5 +#define BMP280_DELAYTIME_MS_6 6 +#define BMP280_DELAYTIME_MS_8 8 +#define BMP280_DELAYTIME_MS_12 12 +#define BMP280_DELAYTIME_MS_22 22 +#define BMP280_DELAYTIME_MS_38 38 + +#define BMP280_OVERSAMPLING_SKIPPED 0x00 +#define BMP280_OVERSAMPLING_1X 0x01 +#define BMP280_OVERSAMPLING_2X 0x02 +#define BMP280_OVERSAMPLING_4X 0x03 +#define BMP280_OVERSAMPLING_8X 0x04 +#define BMP280_OVERSAMPLING_16X 0x05 + +#define BMP280_ULTRALOWPOWER_MODE 0x00 +#define BMP280_LOWPOWER_MODE 0x01 +#define BMP280_STANDARDRESOLUTION_MODE 0x02 +#define BMP280_HIGHRESOLUTION_MODE 0x03 +#define BMP280_ULTRAHIGHRESOLUTION_MODE 0x04 + +#define BMP280_ULTRALOWPOWER_OSRS_P BMP280_OVERSAMPLING_1X +#define BMP280_ULTRALOWPOWER_OSRS_T BMP280_OVERSAMPLING_1X + +#define BMP280_LOWPOWER_OSRS_P BMP280_OVERSAMPLING_2X +#define BMP280_LOWPOWER_OSRS_T BMP280_OVERSAMPLING_1X + +#define BMP280_STANDARDRESOLUTION_OSRS_P BMP280_OVERSAMPLING_4X +#define BMP280_STANDARDRESOLUTION_OSRS_T BMP280_OVERSAMPLING_1X + +#define BMP280_HIGHRESOLUTION_OSRS_P BMP280_OVERSAMPLING_8X +#define BMP280_HIGHRESOLUTION_OSRS_T BMP280_OVERSAMPLING_1X + +#define BMP280_ULTRAHIGHRESOLUTION_OSRS_P BMP280_OVERSAMPLING_16X +#define BMP280_ULTRAHIGHRESOLUTION_OSRS_T BMP280_OVERSAMPLING_2X + +#define BMP280_FILTERCOEFF_OFF 0x00 +#define BMP280_FILTERCOEFF_2 0x01 +#define BMP280_FILTERCOEFF_4 0x02 +#define BMP280_FILTERCOEFF_8 0x03 +#define BMP280_FILTERCOEFF_16 0x04 + +/*calibration parameters */ +#define BMP280_DIG_T1_LSB_REG 0x88 +#define BMP280_DIG_T1_MSB_REG 0x89 +#define BMP280_DIG_T2_LSB_REG 0x8A +#define BMP280_DIG_T2_MSB_REG 0x8B +#define BMP280_DIG_T3_LSB_REG 0x8C +#define BMP280_DIG_T3_MSB_REG 0x8D +#define BMP280_DIG_P1_LSB_REG 0x8E +#define BMP280_DIG_P1_MSB_REG 0x8F +#define BMP280_DIG_P2_LSB_REG 0x90 +#define BMP280_DIG_P2_MSB_REG 0x91 +#define BMP280_DIG_P3_LSB_REG 0x92 +#define BMP280_DIG_P3_MSB_REG 0x93 +#define BMP280_DIG_P4_LSB_REG 0x94 +#define BMP280_DIG_P4_MSB_REG 0x95 +#define BMP280_DIG_P5_LSB_REG 0x96 +#define BMP280_DIG_P5_MSB_REG 0x97 +#define BMP280_DIG_P6_LSB_REG 0x98 +#define BMP280_DIG_P6_MSB_REG 0x99 +#define BMP280_DIG_P7_LSB_REG 0x9A +#define BMP280_DIG_P7_MSB_REG 0x9B +#define BMP280_DIG_P8_LSB_REG 0x9C +#define BMP280_DIG_P8_MSB_REG 0x9D +#define BMP280_DIG_P9_LSB_REG 0x9E +#define BMP280_DIG_P9_MSB_REG 0x9F + +#define BMP280_CHIPID_REG 0xD0 /*Chip ID Register */ +#define BMP280_RESET_REG 0xE0 /*Softreset Register */ +#define BMP280_STATUS_REG 0xF3 /*Status Register */ +#define BMP280_CTRLMEAS_REG 0xF4 /*Ctrl Measure Register */ +#define BMP280_CONFIG_REG 0xF5 /*Configuration Register */ +#define BMP280_PRESSURE_MSB_REG 0xF7 /*Pressure MSB Register */ +#define BMP280_PRESSURE_LSB_REG 0xF8 /*Pressure LSB Register */ +#define BMP280_PRESSURE_XLSB_REG 0xF9 /*Pressure XLSB Register */ +#define BMP280_TEMPERATURE_MSB_REG 0xFA /*Temperature MSB Reg */ +#define BMP280_TEMPERATURE_LSB_REG 0xFB /*Temperature LSB Reg */ +#define BMP280_TEMPERATURE_XLSB_REG 0xFC /*Temperature XLSB Reg */ + +/* Status Register */ +#define BMP280_STATUS_REG_MEASURING__POS 3 +#define BMP280_STATUS_REG_MEASURING__MSK 0x08 +#define BMP280_STATUS_REG_MEASURING__LEN 1 +#define BMP280_STATUS_REG_MEASURING__REG BMP280_STATUS_REG + +#define BMP280_STATUS_REG_IMUPDATE__POS 0 +#define BMP280_STATUS_REG_IMUPDATE__MSK 0x01 +#define BMP280_STATUS_REG_IMUPDATE__LEN 1 +#define BMP280_STATUS_REG_IMUPDATE__REG BMP280_STATUS_REG + +/* Control Measurement Register */ +#define BMP280_CTRLMEAS_REG_OSRST__POS 5 +#define BMP280_CTRLMEAS_REG_OSRST__MSK 0xE0 +#define BMP280_CTRLMEAS_REG_OSRST__LEN 3 +#define BMP280_CTRLMEAS_REG_OSRST__REG BMP280_CTRLMEAS_REG + +#define BMP280_CTRLMEAS_REG_OSRSP__POS 2 +#define BMP280_CTRLMEAS_REG_OSRSP__MSK 0x1C +#define BMP280_CTRLMEAS_REG_OSRSP__LEN 3 +#define BMP280_CTRLMEAS_REG_OSRSP__REG BMP280_CTRLMEAS_REG + +#define BMP280_CTRLMEAS_REG_MODE__POS 0 +#define BMP280_CTRLMEAS_REG_MODE__MSK 0x03 +#define BMP280_CTRLMEAS_REG_MODE__LEN 2 +#define BMP280_CTRLMEAS_REG_MODE__REG BMP280_CTRLMEAS_REG + +/* Configuation Register */ +#define BMP280_CONFIG_REG_TSB__POS 5 +#define BMP280_CONFIG_REG_TSB__MSK 0xE0 +#define BMP280_CONFIG_REG_TSB__LEN 3 +#define BMP280_CONFIG_REG_TSB__REG BMP280_CONFIG_REG + +#define BMP280_CONFIG_REG_FILTER__POS 2 +#define BMP280_CONFIG_REG_FILTER__MSK 0x1C +#define BMP280_CONFIG_REG_FILTER__LEN 3 +#define BMP280_CONFIG_REG_FILTER__REG BMP280_CONFIG_REG + +#define BMP280_CONFIG_REG_SPI3WEN__POS 0 +#define BMP280_CONFIG_REG_SPI3WEN__MSK 0x01 +#define BMP280_CONFIG_REG_SPI3WEN__LEN 1 +#define BMP280_CONFIG_REG_SPI3WEN__REG BMP280_CONFIG_REG + +/* Data Register */ +#define BMP280_PRESSURE_XLSB_REG_DATA__POS 4 +#define BMP280_PRESSURE_XLSB_REG_DATA__MSK 0xF0 +#define BMP280_PRESSURE_XLSB_REG_DATA__LEN 4 +#define BMP280_PRESSURE_XLSB_REG_DATA__REG BMP280_PRESSURE_XLSB_REG + +#define BMP280_TEMPERATURE_XLSB_REG_DATA__POS 4 +#define BMP280_TEMPERATURE_XLSB_REG_DATA__MSK 0xF0 +#define BMP280_TEMPERATURE_XLSB_REG_DATA__LEN 4 +#define BMP280_TEMPERATURE_XLSB_REG_DATA__REG BMP280_TEMPERATURE_XLSB_REG + +#define BMP280_RATE_SCALE 100 +#define DATA_BMP280_MIN_READ_TIME (97 * NSEC_PER_MSEC) +#define BMP280_DATA_BYTES 6 +#define FAKE_DATA_NUM_BYTES 10 + +/** this structure holds all device specific calibration parameters */ +struct bmp280_calibration_param_t { + u32 dig_T1; + s32 dig_T2; + s32 dig_T3; + u32 dig_P1; + s32 dig_P2; + s32 dig_P3; + s32 dig_P4; + s32 dig_P5; + s32 dig_P6; + s32 dig_P7; + s32 dig_P8; + s32 dig_P9; + + s32 t_fine; +}; +/** BMP280 image registers data structure */ +struct bmp280_t { + struct bmp280_calibration_param_t cal_param; + + u8 chip_id; + u8 dev_addr; + + u8 waittime; + + u8 osrs_t; + u8 osrs_p; +}; +static struct bmp280_t bmp280; + +static int bmp280_get_calib_param(struct inv_mpu_state *st) +{ + u8 d[24]; + int r; + + r = inv_aux_read(BMP280_DIG_T1_LSB_REG, 24, d); + if (r) + return r; + + bmp280.cal_param.dig_T1 = (u16)((((u16)((u8)d[1])) << + SHIFT_LEFT_8_POSITION) | d[0]); + bmp280.cal_param.dig_T2 = (s16)((((s16)((s8)d[3])) << + SHIFT_LEFT_8_POSITION) | d[2]); + bmp280.cal_param.dig_T3 = (s16)((((s16)((s8)d[5])) << + SHIFT_LEFT_8_POSITION) | d[4]); + bmp280.cal_param.dig_P1 = (u16)((((u16)((u8)d[7])) << + SHIFT_LEFT_8_POSITION) | d[6]); + bmp280.cal_param.dig_P2 = (s16)((((s16)((s8)d[9])) << + SHIFT_LEFT_8_POSITION) | d[8]); + bmp280.cal_param.dig_P3 = (s16)((((s16)((s8)d[11])) << + SHIFT_LEFT_8_POSITION) | d[10]); + bmp280.cal_param.dig_P4 = (s16)((((s16)((s8)d[13])) << + SHIFT_LEFT_8_POSITION) | d[12]); + bmp280.cal_param.dig_P5 = (s16)((((s16)((s8)d[15])) << + SHIFT_LEFT_8_POSITION) | d[14]); + bmp280.cal_param.dig_P6 = (s16)((((s16)((s8)d[17])) << + SHIFT_LEFT_8_POSITION) | d[16]); + bmp280.cal_param.dig_P7 = (s16)((((s16)((s8)d[19])) << + SHIFT_LEFT_8_POSITION) | d[18]); + bmp280.cal_param.dig_P8 = (s16)((((s16)((s8)d[21])) << + SHIFT_LEFT_8_POSITION) | d[20]); + bmp280.cal_param.dig_P9 = (s16)((((s16)((s8)d[23])) << + SHIFT_LEFT_8_POSITION) | d[22]); + + return 0; +} + +static int inv_setup_bmp280(struct inv_mpu_state *st) +{ + int r; + u8 d[10]; + + /* set to bypass mode */ + r = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config | BIT_BYPASS_EN); + if (r) + return r; + /* issue soft reset */ + r = inv_aux_write(BMP280_RESET_REG, BMP280_SOFT_RESET); + if (r) + return r; + msleep(100); + r = inv_aux_read(BMP280_CHIPID_REG, 1, d); + if (r) + return r; + /* set pressure as ultra high resolution */ + bmp280.osrs_t = BMP280_ULTRAHIGHRESOLUTION_OSRS_T; + bmp280.osrs_p = BMP280_ULTRAHIGHRESOLUTION_OSRS_P; + + /* set IIR filter as 4 */ + r = inv_aux_write(BMP280_CONFIG_REG_FILTER__REG, + BMP280_FILTERCOEFF_4 << SHIFT_LEFT_2_POSITION); + if (r) + return r; + r = bmp280_get_calib_param(st); + if (r) + return r; + + /*restore to non-bypass mode */ + r = inv_i2c_single_write(st, REG_INT_PIN_CFG, + st->plat_data.int_config); + if (r) + return r; + + /* setup master mode and master clock and ES bit */ + r = inv_i2c_single_write(st, REG_I2C_MST_CTRL, BIT_WAIT_FOR_ES); + if (r) + return r; + /*slave 3 is used for pressure mode change only*/ + r = inv_i2c_single_write(st, REG_I2C_SLV3_ADDR, + st->plat_data.aux_i2c_addr); + if (r) + return r; + /* pressure sensor mode register address */ + r = inv_i2c_single_write(st, REG_I2C_SLV3_REG, BMP280_CTRLMEAS_REG); + if (r) + return r; + d[0] = (bmp280.osrs_t << SHIFT_LEFT_5_POSITION) + + (bmp280.osrs_p << SHIFT_LEFT_2_POSITION) + + BMP280_FORCED_MODE; + r = inv_i2c_single_write(st, INV_MPU_REG_I2C_SLV3_DO, d[0]); + + return r; +} + +static int inv_check_bmp280_self_test(struct inv_mpu_state *st) +{ + return 0; +} +static int inv_write_bmp280_scale(struct inv_mpu_state *st, int data) +{ + return 0; +} +static int inv_read_bmp280_scale(struct inv_mpu_state *st, int *scale) +{ + return 0; +} + +static int inv_resume_bmp280(struct inv_mpu_state *st) +{ + int r; + + if ((!st->sensor[SENSOR_COMPASS].on) && st->chip_config.dmp_on) { + /* if compass is disabled, read fake data for DMP */ + /*read mode */ + r = inv_i2c_single_write(st, REG_I2C_SLV0_ADDR, + INV_MPU_BIT_I2C_READ | + st->plat_data.aux_i2c_addr); + if (r) + return r; + /* read calibration data as the fake data */ + r = inv_i2c_single_write(st, REG_I2C_SLV0_REG, + BMP280_DIG_T1_LSB_REG); + if (r) + return r; + /* slave 0 is enabled, read 10 bytes from here */ + r = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, + INV_MPU_BIT_SLV_EN | + FAKE_DATA_NUM_BYTES); + } + + /* slave 2 is used to read data from pressure sensor */ + /*read mode */ + r = inv_i2c_single_write(st, REG_I2C_SLV2_ADDR, + INV_MPU_BIT_I2C_READ | + st->plat_data.aux_i2c_addr); + if (r) + return r; + /* start from pressure sensor */ + r = inv_i2c_single_write(st, REG_I2C_SLV2_REG, + BMP280_PRESSURE_MSB_REG); + if (r) + return r; + + /* slave 2 is enabled, read 6 bytes from here */ + r = inv_i2c_single_write(st, REG_I2C_SLV2_CTRL, + INV_MPU_BIT_SLV_EN | BMP280_DATA_BYTES); + if (r) + return r; + /* slave 3 is enabled, write byte length is 1 */ + r = inv_i2c_single_write(st, REG_I2C_SLV3_CTRL, + INV_MPU_BIT_SLV_EN | 1); + + return r; +} + +static int inv_suspend_bmp280(struct inv_mpu_state *st) +{ + int r; + + if ((!st->sensor[SENSOR_COMPASS].on) && st->chip_config.dmp_on) { + /* slave 0 is disabled */ + r = inv_i2c_single_write(st, REG_I2C_SLV0_CTRL, 0); + if (r) + return r; + } + + /* slave 2 is disabled */ + r = inv_i2c_single_write(st, REG_I2C_SLV2_CTRL, 0); + if (r) + return r; + /* slave 3 is disabled */ + r = inv_i2c_single_write(st, REG_I2C_SLV3_CTRL, 0); + + return r; +} + +static s32 bmp280_compensate_T_int32(s32 adc_T) +{ + s32 v_x1_u32r = 0; + s32 v_x2_u32r = 0; + s32 temperature = 0; + + v_x1_u32r = ((((adc_T >> 3) - ((s32) + bmp280.cal_param.dig_T1 << 1))) * + ((s32)bmp280.cal_param.dig_T2)) >> 11; + v_x2_u32r = (((((adc_T >> 4) - + ((s32)bmp280.cal_param.dig_T1)) * ((adc_T >> 4) - + ((s32)bmp280.cal_param.dig_T1))) >> 12) * + ((s32)bmp280.cal_param.dig_T3)) >> 14; + bmp280.cal_param.t_fine = v_x1_u32r + v_x2_u32r; + temperature = (bmp280.cal_param.t_fine * 5 + 128) >> 8; + + return temperature; +} + + +static u32 bmp280_compensate_P_int32(s32 adc_P) +{ + s32 v_x1_u32r = 0; + s32 v_x2_u32r = 0; + u32 pressure = 0; + + v_x1_u32r = (((s32)bmp280.cal_param.t_fine) >> 1) - + (s32)64000; + v_x2_u32r = (((v_x1_u32r >> 2) * (v_x1_u32r >> 2)) >> 11) * + ((s32)bmp280.cal_param.dig_P6); + v_x2_u32r = v_x2_u32r + ((v_x1_u32r * + ((s32)bmp280.cal_param.dig_P5)) << 1); + v_x2_u32r = (v_x2_u32r >> 2) + + (((s32)bmp280.cal_param.dig_P4) << 16); + v_x1_u32r = (((bmp280.cal_param.dig_P3 * (((v_x1_u32r >> 2) * + (v_x1_u32r >> 2)) >> 13)) >> 3) + + ((((s32)bmp280.cal_param.dig_P2) * + v_x1_u32r) >> 1)) >> 18; + v_x1_u32r = ((((32768+v_x1_u32r)) * + ((s32)bmp280.cal_param.dig_P1)) >> 15); + /* Avoid exception caused by division by zero */ + if (v_x1_u32r == 0) + return 0; + pressure = (((u32)(((s32)1048576) - adc_P) - + (v_x2_u32r >> 12))) * 3125; + if (pressure < 0x80000000) + pressure = (pressure << 1) / ((u32)v_x1_u32r); + else + pressure = (pressure / (u32)v_x1_u32r) * 2; + v_x1_u32r = (((s32)bmp280.cal_param.dig_P9) * + ((s32)(((pressure >> 3) * (pressure >> 3)) >> 13))) + >> 12; + v_x2_u32r = (((s32)(pressure >> 2)) * + ((s32)bmp280.cal_param.dig_P8)) >> 13; + pressure = (u32)((s32)pressure + + ((v_x1_u32r + v_x2_u32r + bmp280.cal_param.dig_P7) >> 4)); + + return pressure; +} + +static int inv_bmp280_read_data(struct inv_mpu_state *st, short *o) +{ + int r, i; + u8 d[BMP280_DATA_BYTES], reg_addr; + s32 upressure, utemperature; + + if (st->chip_config.dmp_on) { + for (i = 0; i < 6; i++) + d[i] = st->fifo_data[i]; + } else { + if (st->sensor[SENSOR_COMPASS].on) + reg_addr = REG_EXT_SENS_DATA_08; + else + reg_addr = REG_EXT_SENS_DATA_00; + r = inv_i2c_read(st, reg_addr, BMP280_DATA_BYTES, d); + if (r) + return r; + } + /* pressure */ + upressure = (s32)((((s32)(d[0])) + << SHIFT_LEFT_12_POSITION) | (((u32)(d[1])) + << SHIFT_LEFT_4_POSITION) | ((u32)d[2] >> + SHIFT_RIGHT_4_POSITION)); + + /* Temperature */ + utemperature = (s32)((( + (s32) (d[3])) << SHIFT_LEFT_12_POSITION) | + (((u32)(d[4])) << SHIFT_LEFT_4_POSITION) + | ((u32)d[5] >> SHIFT_RIGHT_4_POSITION)); + + bmp280_compensate_T_int32(utemperature); + r = bmp280_compensate_P_int32(upressure); + o[0] = 0; + o[1] = (r >> 16); + o[2] = (r & 0xffff); + + return r; +} + +static struct inv_mpu_slave slave_bmp280 = { + .suspend = inv_suspend_bmp280, + .resume = inv_resume_bmp280, + .get_scale = inv_read_bmp280_scale, + .set_scale = inv_write_bmp280_scale, + .self_test = inv_check_bmp280_self_test, + .setup = inv_setup_bmp280, + .read_data = inv_bmp280_read_data, + .rate_scale = BMP280_RATE_SCALE, + .min_read_time = DATA_BMP280_MIN_READ_TIME, +}; + +int inv_mpu_setup_pressure_slave(struct inv_mpu_state *st) +{ + switch (st->plat_data.aux_slave_id) { + case PRESSURE_ID_BMP280: + st->slave_pressure = &slave_bmp280; + break; + default: + return -EINVAL; + } + + return st->slave_pressure->setup(st); +} + diff --git a/drivers/memory/ti-emif-sram-pm.S b/drivers/memory/ti-emif-sram-pm.S index 63f62018b1c02..9fb1bc5f59674 100644 --- a/drivers/memory/ti-emif-sram-pm.S +++ b/drivers/memory/ti-emif-sram-pm.S @@ -51,54 +51,95 @@ ENTRY(ti_emif_save_context) /* Save EMIF configuration */ ldr r1, [r0, #EMIF_SDRAM_CONFIG] - str r1, emif_sdcfg_val +ARM( str r1, emif_sdcfg_val ) +THUMB( adr r2, emif_sdcfg_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_SDRAM_REFRESH_CONTROL] - str r1, emif_ref_ctrl_val +ARM( str r1, emif_ref_ctrl_val ) +THUMB( adr r2, emif_ref_ctrl_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_SDRAM_TIMING_1] - str r1, emif_timing1_val +ARM( str r1, emif_timing1_val ) +THUMB( adr r2, emif_timing1_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_SDRAM_TIMING_2] - str r1, emif_timing2_val +ARM( str r1, emif_timing2_val ) +THUMB( adr r2, emif_timing2_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_SDRAM_TIMING_3] - str r1, emif_timing3_val +ARM( str r1, emif_timing3_val ) +THUMB( adr r2, emif_timing3_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] - str r1, emif_pmcr_val +ARM( str r1, emif_pmcr_val ) +THUMB( adr r2, emif_pmcr_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_POWER_MANAGEMENT_CTRL_SHDW] - str r1, emif_pmcr_shdw_val +ARM( str r1, emif_pmcr_shdw_val ) +THUMB( adr r2, emif_pmcr_shdw_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG] - str r1, emif_zqcfg_val +ARM( str r1, emif_zqcfg_val ) +THUMB( adr r2, emif_zqcfg_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_DDR_PHY_CTRL_1] - str r1, emif_ddr_phy_ctlr_1 +ARM( str r1, emif_ddr_phy_ctlr_1 ) +THUMB( adr r2, emif_ddr_phy_ctlr_1 ) +THUMB( str r1, [r2] ) ldr r2, ti_emif_sram_config cmp r2, #EMIF_SRAM_AM43_REG_LAYOUT bne emif_skip_save_extra_regs ldr r1, [r0, #EMIF_READ_WRITE_LEVELING_RAMP_CONTROL] - str r1, emif_rd_wr_level_ramp_ctrl +ARM( str r1, emif_rd_wr_level_ramp_ctrl ) +THUMB( adr r2, emif_rd_wr_level_ramp_ctrl ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_READ_WRITE_EXECUTION_THRESHOLD] - str r1, emif_rd_wr_exec_thresh +ARM( str r1, emif_rd_wr_exec_thresh ) +THUMB( adr r2, emif_rd_wr_exec_thresh ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_COS_CONFIG] - str r1, emif_cos_config +ARM( str r1, emif_cos_config ) +THUMB( adr r2, emif_cos_config ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_PRIORITY_TO_CLASS_OF_SERVICE_MAPPING] - str r1, emif_priority_to_cos_mapping +ARM( str r1, emif_priority_to_cos_mapping ) +THUMB( adr r2, emif_priority_to_cos_mapping ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_1_MAPPING] - str r1, emif_connect_id_serv_1_map +ARM( str r1, emif_connect_id_serv_1_map ) +THUMB( adr r2, emif_connect_id_serv_1_map ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_2_MAPPING] - str r1, emif_connect_id_serv_2_map +ARM( str r1, emif_connect_id_serv_2_map ) +THUMB( adr r2, emif_connect_id_serv_2_map ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_LPDDR2_NVM_TIMING] - str r1, emif_lpddr2_nvm_tim +ARM( str r1, emif_lpddr2_nvm_tim ) +THUMB( adr r2, emif_lpddr2_nvm_tim ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_LPDDR2_NVM_TIMING_SHDW] - str r1, emif_lpddr2_nvm_tim_shdw +ARM( str r1, emif_lpddr2_nvm_tim_shdw ) +THUMB( adr r2, emif_lpddr2_nvm_tim_shdw ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_OCP_CONFIG] - str r1, emif_ocp_config_val +ARM( str r1, emif_ocp_config_val ) +THUMB( adr r2, emif_ocp_config_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_DLL_CALIB_CTRL] - str r1, emif_dll_calib_ctrl_val +ARM( str r1, emif_dll_calib_ctrl_val ) +THUMB( adr r2, emif_dll_calib_ctrl_val ) +THUMB( str r1, [r2] ) ldr r1, [r0, #EMIF_DLL_CALIB_CTRL_SHDW] - str r1, emif_dll_calib_ctrl_val_shdw +ARM( str r1, emif_dll_calib_ctrl_val_shdw ) +THUMB( adr r2, emif_dll_calib_ctrl_val_shdw ) +THUMB( str r1, [r2] ) /* Loop and save entire block of emif phy regs */ mov r2, #0x0 - adrl r4, emif_ext_phy_ctrl_vals +ARM( adrl r4, emif_ext_phy_ctrl_vals ) +THUMB( adr r4, emif_ext_phy_ctrl_vals ) add r3, r0, #EMIF_EXT_PHY_CTRL_1 ddr_phy_ctrl_save: ldr r1, [r3, r2] @@ -175,7 +216,8 @@ ENTRY(ti_emif_restore_context) /* Loop and restore entire block of emif phy regs */ mov r2, #0x0 - adrl r3, emif_ext_phy_ctrl_vals +ARM( adrl r3, emif_ext_phy_ctrl_vals ) +THUMB( adr r3, emif_ext_phy_ctrl_vals ) add r4, r0, #EMIF_EXT_PHY_CTRL_1 ddr_phy_ctrl_restore: ldr r1, [r3, r2] diff --git a/drivers/mfd/tps65217.c b/drivers/mfd/tps65217.c index 7d1cfc1d3ce00..0730431e1210c 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 @@ -157,6 +161,82 @@ static const struct of_device_id tps65217_of_match[] = { { /* sentinel */ }, }; +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) { @@ -164,10 +244,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, @@ -175,8 +258,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) { @@ -200,6 +306,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 453a61660d2d5..687578d12cc99 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -553,6 +553,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" @@ -563,7 +593,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 6a8e39388cf9c..b4d4defcc7b54 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,7 +53,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 a/drivers/misc/bone_capemgr.c b/drivers/misc/bone_capemgr.c new file mode 100644 index 0000000000000..094ea7559c86b --- /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 a/drivers/misc/cape/Kconfig b/drivers/misc/cape/Kconfig new file mode 100644 index 0000000000000..a2ef85e0415c5 --- /dev/null +++ b/drivers/misc/cape/Kconfig @@ -0,0 +1,5 @@ +# +# Capes +# + +source "drivers/misc/cape/beaglebone/Kconfig" diff --git a/drivers/misc/cape/Makefile b/drivers/misc/cape/Makefile new file mode 100644 index 0000000000000..7c4eb96ab29e4 --- /dev/null +++ b/drivers/misc/cape/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for cape like devices +# + +obj-y += beaglebone/ diff --git a/drivers/misc/cape/beaglebone/Kconfig b/drivers/misc/cape/beaglebone/Kconfig new file mode 100644 index 0000000000000..eeb67828fd829 --- /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 a/drivers/misc/cape/beaglebone/Makefile b/drivers/misc/cape/beaglebone/Makefile new file mode 100644 index 0000000000000..7f4617a01e262 --- /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 a/drivers/misc/cape/beaglebone/bone-pinmux-helper.c b/drivers/misc/cape/beaglebone/bone-pinmux-helper.c new file mode 100644 index 0000000000000..d81363a77b103 --- /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 a/drivers/misc/cape_bone_argus/Kconfig b/drivers/misc/cape_bone_argus/Kconfig new file mode 100644 index 0000000000000..1b396611e447f --- /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 a/drivers/misc/cape_bone_argus/Makefile b/drivers/misc/cape_bone_argus/Makefile new file mode 100644 index 0000000000000..5482562177766 --- /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 a/drivers/misc/cape_bone_argus/cape_bone_argus.c b/drivers/misc/cape_bone_argus/cape_bone_argus.c new file mode 100644 index 0000000000000..c434218d93de1 --- /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 a/drivers/misc/devovmgr.c b/drivers/misc/devovmgr.c new file mode 100644 index 0000000000000..82d2b077d8d62 --- /dev/null +++ b/drivers/misc/devovmgr.c @@ -0,0 +1,1305 @@ +/* + * 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; +} + + +CONFIGFS_ATTR_STRUCT(dovmgr_item); +#define DOVMGR_ITEM_ATTR(_name, _mode, _show, _store) \ +struct dovmgr_item_attribute dovmgr_item_attr_##_name = \ + __CONFIGFS_ATTR(_name, _mode, _show, _store) +#define DOVMGR_ITEM_ATTR_RO(_name, _show) \ +struct dovmgr_item_attribute dovmgr_item_attr_##_name = \ + __CONFIGFS_ATTR_RO(_name, _show) + +CONFIGFS_BIN_ATTR_STRUCT(dovmgr_item); +#define DOVMGR_ITEM_BIN_ATTR(_name, _mode, _read, _write, _priv, _max) \ +struct dovmgr_item_bin_attribute dovmgr_item_bin_attr_##_name = \ + __CONFIGFS_BIN_ATTR(_name, _mode, _read, _write, _priv, _max) +#define DOVMGR_ITEM_BIN_ATTR_RO(_name, _read, _priv, _max) \ +struct dovmgr_item_bin_attribute dovmgr_item_bin_attr_##_name = \ + __CONFIGFS_BIN_ATTR_RO(_name, _read, _priv, _max) + +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 dovmgr_item *item, char *page) +{ + return dovmgr_item_str_show(item, page, &item->path); +} + +static ssize_t dovmgr_item_path_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + return dovmgr_item_str_store(item, page, count, &item->path); +} + +static ssize_t dovmgr_item_enable_show(struct dovmgr_item *item, char *page) +{ + return snprintf(page, PAGE_SIZE, "%u\n", !!item->enable); +} + +static ssize_t dovmgr_item_enable_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, char *page) +{ + 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 dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, char *page) +{ + 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; +} + +DOVMGR_ITEM_ATTR(path, S_IRUGO | S_IWUSR, + dovmgr_item_path_show, dovmgr_item_path_store); +DOVMGR_ITEM_ATTR_RO(status, dovmgr_item_status_show); +DOVMGR_ITEM_ATTR(enable, S_IRUGO | S_IWUSR, + dovmgr_item_enable_show, dovmgr_item_enable_store); +DOVMGR_ITEM_ATTR(overlay, S_IRUGO | S_IWUSR, + dovmgr_item_overlay_show, dovmgr_item_overlay_store); + +#if IS_ENABLED(CONFIG_PCI) +static ssize_t dovmgr_item_pci_device_show(struct dovmgr_item *item, char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.device); +} + +static ssize_t dovmgr_item_pci_device_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.vendor); +} + +static ssize_t dovmgr_item_pci_vendor_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%08x\n", item->pci.subdevice); +} + +static ssize_t dovmgr_item_pci_subdevice_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%08x\n", item->pci.subvendor); +} + +static ssize_t dovmgr_item_pci_subvendor_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.class); +} + +static ssize_t dovmgr_item_pci_class_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", item->pci.class_mask); +} + +static ssize_t dovmgr_item_pci_class_mask_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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; +} + +DOVMGR_ITEM_ATTR(device, S_IRUGO | S_IWUSR, + dovmgr_item_pci_device_show, dovmgr_item_pci_device_store); +DOVMGR_ITEM_ATTR(vendor, S_IRUGO | S_IWUSR, + dovmgr_item_pci_vendor_show, dovmgr_item_pci_vendor_store); +DOVMGR_ITEM_ATTR(subdevice, S_IRUGO | S_IWUSR, + dovmgr_item_pci_subdevice_show, dovmgr_item_pci_subdevice_store); +DOVMGR_ITEM_ATTR(subvendor, S_IRUGO | S_IWUSR, + dovmgr_item_pci_subvendor_show, dovmgr_item_pci_subvendor_store); +DOVMGR_ITEM_ATTR(class, S_IRUGO | S_IWUSR, + dovmgr_item_pci_class_show, dovmgr_item_pci_class_store); +DOVMGR_ITEM_ATTR(class_mask, S_IRUGO | S_IWUSR, + dovmgr_item_pci_class_mask_show, dovmgr_item_pci_class_mask_store); +#endif + +#if IS_ENABLED(CONFIG_USB) +static ssize_t dovmgr_item_usb_idProduct_show(struct dovmgr_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", + item->usb.idProduct); +} + +static ssize_t dovmgr_item_usb_idProduct_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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 dovmgr_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "0x%04x\n", + item->usb.idVendor); +} + +static ssize_t dovmgr_item_usb_idVendor_store(struct dovmgr_item *item, + const char *page, size_t count) +{ + 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; +} + +DOVMGR_ITEM_ATTR(idProduct, S_IRUGO | S_IWUSR, + dovmgr_item_usb_idProduct_show, dovmgr_item_usb_idProduct_store); +DOVMGR_ITEM_ATTR(idVendor, S_IRUGO | S_IWUSR, + dovmgr_item_usb_idVendor_show, dovmgr_item_usb_idVendor_store); +#endif + +#if IS_ENABLED(CONFIG_PCI) +static struct configfs_attribute *dovmgr_pci_attrs[] = { + &dovmgr_item_attr_path.attr, + &dovmgr_item_attr_status.attr, + &dovmgr_item_attr_enable.attr, + &dovmgr_item_attr_overlay.attr, + &dovmgr_item_attr_device.attr, + &dovmgr_item_attr_vendor.attr, + &dovmgr_item_attr_subdevice.attr, + &dovmgr_item_attr_subvendor.attr, + &dovmgr_item_attr_class.attr, + &dovmgr_item_attr_class_mask.attr, + NULL, +}; +#endif + +#if IS_ENABLED(CONFIG_USB) +static struct configfs_attribute *dovmgr_usb_attrs[] = { + &dovmgr_item_attr_path.attr, + &dovmgr_item_attr_enable.attr, + &dovmgr_item_attr_status.attr, + &dovmgr_item_attr_overlay.attr, + &dovmgr_item_attr_idVendor.attr, + &dovmgr_item_attr_idProduct.attr, + 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); +} + +CONFIGFS_ATTR_OPS(dovmgr_item); +static struct configfs_item_operations dovmgr_item_ops = { + .release = dovmgr_release, + .show_attribute = dovmgr_item_attr_show, + .store_attribute = dovmgr_item_attr_store, +}; + +#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 9536852fd4c68..04f2e1fa9dd15 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -96,17 +96,4 @@ config EEPROM_DIGSY_MTC_CFG If unsure, say N. -config EEPROM_SUNXI_SID - tristate "Allwinner sunxi security ID support" - depends on ARCH_SUNXI && SYSFS - help - This is a driver for the 'security ID' available on various Allwinner - devices. - - Due to the potential risks involved with changing e-fuses, - this driver is read-only. - - This driver can also be built as a module. If so, the module - will be called sunxi_sid. - endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 9507aec95e948..fc1e81d292673 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -4,5 +4,4 @@ obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o obj-$(CONFIG_EEPROM_MAX6875) += max6875.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o -obj-$(CONFIG_EEPROM_SUNXI_SID) += sunxi_sid.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o diff --git a/drivers/misc/eeprom/at24.c b/drivers/misc/eeprom/at24.c index 2d3db81be0990..004dfdbc3229e 100644 --- a/drivers/misc/eeprom/at24.c +++ b/drivers/misc/eeprom/at24.c @@ -3,6 +3,7 @@ * * Copyright (C) 2005-2007 David Brownell * Copyright (C) 2008 Wolfram Sang, Pengutronix + * Copyright (C) 2015 Pantelis Antoniou, Konsulko Group * * 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 @@ -23,6 +24,9 @@ #include #include #include +#include +#include +#include /* * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. @@ -63,12 +67,16 @@ 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 regmap *regmap; + struct nvmem_config *nvmem_config; + struct nvmem_device *nvmem_dev; + /* * Some chips tie up multiple I2C addresses; dummy devices reserve * them for us, and we'll use them with SMBus calls. @@ -131,6 +139,8 @@ static const struct i2c_device_id at24_ids[] = { }; MODULE_DEVICE_TABLE(i2c, at24_ids); +static DEFINE_IDA(at24_ida); + /*-------------------------------------------------------------------------*/ /* @@ -301,17 +311,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 @@ -432,21 +431,10 @@ 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; - - if (unlikely(off >= attr->size)) - return -EFBIG; - - at24 = dev_get_drvdata(container_of(kobj, struct device, kobj)); - return at24_write(at24, buf, off, count); -} - /*-------------------------------------------------------------------------*/ +/* panto: using the EEPROM framework macc is superfluous */ + /* * This lets other kernel code access the eeprom data. For example, it * might hold a board's Ethernet address, or board-specific calibration @@ -492,6 +480,91 @@ static void at24_get_ofdata(struct i2c_client *client, { } #endif /* CONFIG_OF */ +static int regmap_at24_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct i2c_client *client = context; + struct at24_data *at24; + unsigned int offset; + int ret; + + /* reg bits is hardcoded to 32 bits */ + BUG_ON(reg_size != 4); + offset = __raw_readl(reg); + + at24 = i2c_get_clientdata(client); + if (at24 == NULL) + return -ENODEV; + + /* val bytes is always 1 */ + BUG_ON(at24->regmap_config->val_bits != 8); + + ret = at24_read(at24, val, offset, val_size); + if (ret < 0) + return ret; + if (ret != val_size) + return -EINVAL; + + return 0; +} + +static int regmap_at24_gather_write(void *context, + const void *reg, size_t reg_size, + const void *val, size_t val_size) +{ + struct i2c_client *client = context; + struct at24_data *at24; + unsigned int offset; + int ret; + + at24 = i2c_get_clientdata(client); + if (at24 == NULL) + return -ENODEV; + + BUG_ON(reg_size != 4); + offset = __raw_readl(reg); + + /* val bytes is always 1 */ + BUG_ON(at24->regmap_config->val_bits != 8); + + ret = at24_write(at24, val, offset, val_size); + if (ret < 0) + return ret; + if (ret != val_size) + return -EINVAL; + + return 0; +} + +static int regmap_at24_write(void *context, const void *data, size_t count) +{ + struct i2c_client *client = context; + struct at24_data *at24; + unsigned int reg_bytes, offset; + + at24 = i2c_get_clientdata(client); + if (at24 == NULL) + return -ENODEV; + + reg_bytes = at24->regmap_config->reg_bits / 8; + offset = reg_bytes; + + BUG_ON(reg_bytes != 4); + BUG_ON(count <= offset); + + return regmap_at24_gather_write(context, data, reg_bytes, + data + offset, count - offset); +} + +static struct regmap_bus regmap_at24_bus = { + .read = regmap_at24_read, + .write = regmap_at24_write, + .gather_write = regmap_at24_gather_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct at24_platform_data chip; @@ -502,6 +575,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) int err; unsigned i, num_addresses; kernel_ulong_t magic; + struct regmap_config *regmap_config; + struct regmap *regmap; + struct nvmem_config *nvmem_config; + struct nvmem_device *nvmem_dev; if (client->dev.platform_data) { chip = *(struct at24_platform_data *)client->dev.platform_data; @@ -569,16 +646,75 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) } } + regmap_config = devm_kzalloc(&client->dev, sizeof(*regmap_config), + GFP_KERNEL); + if (IS_ERR(regmap_config)) { + err = PTR_ERR(regmap_config); + dev_err(&client->dev, "%s: regmap_config allocation failed (%d)\n", + __func__, err); + return err; + } + + /* use 32 bits as registers, they don't appear on the wire anyway */ + regmap_config->reg_bits = 32; + regmap_config->val_bits = 8; + regmap_config->cache_type = REGCACHE_NONE; + regmap_config->max_register = chip.byte_len; + if (chip.flags & AT24_FLAG_TAKE8ADDR) num_addresses = 8; else num_addresses = DIV_ROUND_UP(chip.byte_len, (chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256); + /* we can't use devm_regmap_init_i2c due to the many i2c clients */ + regmap = devm_regmap_init(&client->dev, ®map_at24_bus, + client, regmap_config); + if (IS_ERR(regmap)) { + err = PTR_ERR(regmap); + dev_err(&client->dev, "%s: regmap allocation failed (%d)\n", + __func__, err); + return err; + } + + nvmem_config = devm_kzalloc(&client->dev, sizeof(*nvmem_config), + GFP_KERNEL); + if (IS_ERR(nvmem_config)) { + err = PTR_ERR(nvmem_config); + dev_err(&client->dev, "%s: nvmem_config allocation failed (%d)\n", + __func__, err); + return err; + } + nvmem_config->dev = &client->dev; + nvmem_config->name = "at24-"; + nvmem_config->id = ida_simple_get(&at24_ida, 0, 0, GFP_KERNEL); + nvmem_config->owner = THIS_MODULE; + if (nvmem_config->id < 0) { + err = nvmem_config->id; + dev_err(&client->dev, "%s: eeprom id allocation failed (%d)\n", + __func__, err); + return err; + } + + nvmem_dev = nvmem_register(nvmem_config); + if (IS_ERR(nvmem_dev)) { + err = PTR_ERR(nvmem_dev); + dev_err(&client->dev, "%s: nvmem_register failed (%d)\n", + __func__, err); + goto err_out; + } + at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) + num_addresses * sizeof(struct i2c_client *), GFP_KERNEL); - if (!at24) - return -ENOMEM; + if (!at24) { + err = -ENOMEM; + goto err_out; + } + + at24->regmap = regmap; + at24->regmap_config = regmap_config; + at24->nvmem_config = nvmem_config; + at24->nvmem_dev = nvmem_dev; mutex_init(&at24->lock); at24->use_smbus = use_smbus; @@ -586,16 +722,6 @@ 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); @@ -606,9 +732,6 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) 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) @@ -618,8 +741,10 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) /* buffer (data + address at the beginning) */ at24->writebuf = devm_kzalloc(&client->dev, write_max + 2, GFP_KERNEL); - if (!at24->writebuf) - return -ENOMEM; + if (!at24->writebuf) { + err = -ENOMEM; + goto err_out; + } } else { dev_warn(&client->dev, "cannot write due to controller restrictions."); @@ -640,14 +765,10 @@ 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) - 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, + at24->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) { @@ -663,10 +784,14 @@ static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id) return 0; err_clients: + for (i = 1; i < num_addresses; i++) if (at24->client[i]) i2c_unregister_device(at24->client[i]); +err_out: + ida_simple_remove(&at24_ida, nvmem_config->id); + return err; } @@ -676,11 +801,12 @@ static int at24_remove(struct i2c_client *client) int i; at24 = i2c_get_clientdata(client); - sysfs_remove_bin_file(&client->dev.kobj, &at24->bin); for (i = 1; i < at24->num_addresses; i++) i2c_unregister_device(at24->client[i]); + nvmem_unregister(at24->nvmem_dev); + return 0; } diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c deleted file mode 100644 index 8385177ff32b0..0000000000000 --- a/drivers/misc/eeprom/sunxi_sid.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2013 Oliver Schinagl - * http://www.linux-sunxi.org - * - * 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. - * - * This driver exposes the Allwinner security ID, efuses exported in byte- - * sized chunks. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRV_NAME "sunxi-sid" - -struct sunxi_sid_data { - void __iomem *reg_base; - unsigned int keysize; -}; - -/* We read the entire key, due to a 32 bit read alignment requirement. Since we - * want to return the requested byte, this results in somewhat slower code and - * uses 4 times more reads as needed but keeps code simpler. Since the SID is - * only very rarely probed, this is not really an issue. - */ -static u8 sunxi_sid_read_byte(const struct sunxi_sid_data *sid_data, - const unsigned int offset) -{ - u32 sid_key; - - if (offset >= sid_data->keysize) - return 0; - - sid_key = ioread32be(sid_data->reg_base + round_down(offset, 4)); - sid_key >>= (offset % 4) * 8; - - return sid_key; /* Only return the last byte */ -} - -static ssize_t sid_read(struct file *fd, struct kobject *kobj, - struct bin_attribute *attr, char *buf, - loff_t pos, size_t size) -{ - struct platform_device *pdev; - struct sunxi_sid_data *sid_data; - int i; - - pdev = to_platform_device(kobj_to_dev(kobj)); - sid_data = platform_get_drvdata(pdev); - - if (pos < 0 || pos >= sid_data->keysize) - return 0; - if (size > sid_data->keysize - pos) - size = sid_data->keysize - pos; - - for (i = 0; i < size; i++) - buf[i] = sunxi_sid_read_byte(sid_data, pos + i); - - return i; -} - -static struct bin_attribute sid_bin_attr = { - .attr = { .name = "eeprom", .mode = S_IRUGO, }, - .read = sid_read, -}; - -static int sunxi_sid_remove(struct platform_device *pdev) -{ - device_remove_bin_file(&pdev->dev, &sid_bin_attr); - dev_dbg(&pdev->dev, "driver unloaded\n"); - - return 0; -} - -static const struct of_device_id sunxi_sid_of_match[] = { - { .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16}, - { .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512}, - {/* sentinel */}, -}; -MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); - -static int sunxi_sid_probe(struct platform_device *pdev) -{ - struct sunxi_sid_data *sid_data; - struct resource *res; - const struct of_device_id *of_dev_id; - u8 *entropy; - unsigned int i; - - sid_data = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_sid_data), - GFP_KERNEL); - if (!sid_data) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sid_data->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(sid_data->reg_base)) - return PTR_ERR(sid_data->reg_base); - - of_dev_id = of_match_device(sunxi_sid_of_match, &pdev->dev); - if (!of_dev_id) - return -ENODEV; - sid_data->keysize = (int)of_dev_id->data; - - platform_set_drvdata(pdev, sid_data); - - sid_bin_attr.size = sid_data->keysize; - if (device_create_bin_file(&pdev->dev, &sid_bin_attr)) - return -ENODEV; - - entropy = kzalloc(sizeof(u8) * sid_data->keysize, GFP_KERNEL); - for (i = 0; i < sid_data->keysize; i++) - entropy[i] = sunxi_sid_read_byte(sid_data, i); - add_device_randomness(entropy, sid_data->keysize); - kfree(entropy); - - dev_dbg(&pdev->dev, "loaded\n"); - - return 0; -} - -static struct platform_driver sunxi_sid_driver = { - .probe = sunxi_sid_probe, - .remove = sunxi_sid_remove, - .driver = { - .name = DRV_NAME, - .of_match_table = sunxi_sid_of_match, - }, -}; -module_platform_driver(sunxi_sid_driver); - -MODULE_AUTHOR("Oliver Schinagl "); -MODULE_DESCRIPTION("Allwinner sunxi security id driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/misc/tieqep.c b/drivers/misc/tieqep.c new file mode 100644 index 0000000000000..a0652f96810c7 --- /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_mdio.c b/drivers/net/ethernet/ti/davinci_mdio.c index 9232506134586..a7e0bb3a5efa9 100644 --- a/drivers/net/ethernet/ti/davinci_mdio.c +++ b/drivers/net/ethernet/ti/davinci_mdio.c @@ -124,6 +124,10 @@ static void __davinci_gpio_reset(struct davinci_mdio_data *data) } } +#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; @@ -180,6 +184,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"); @@ -370,6 +380,93 @@ static int davinci_mdio_probe_dt(struct davinci_mdio_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/net/phy/vitesse.c b/drivers/net/phy/vitesse.c index 76cad712ddb2c..dd295dbaa0746 100644 --- a/drivers/net/phy/vitesse.c +++ b/drivers/net/phy/vitesse.c @@ -66,6 +66,7 @@ #define PHY_ID_VSC8244 0x000fc6c0 #define PHY_ID_VSC8514 0x00070670 #define PHY_ID_VSC8574 0x000704a0 +#define PHY_ID_VSC8601 0x00070420 #define PHY_ID_VSC8662 0x00070660 #define PHY_ID_VSC8221 0x000fc550 #define PHY_ID_VSC8211 0x000fc4b0 @@ -133,7 +134,8 @@ static int vsc82xx_config_intr(struct phy_device *phydev) (phydev->drv->phy_id == PHY_ID_VSC8234 || phydev->drv->phy_id == PHY_ID_VSC8244 || phydev->drv->phy_id == PHY_ID_VSC8514 || - phydev->drv->phy_id == PHY_ID_VSC8574) ? + phydev->drv->phy_id == PHY_ID_VSC8574 || + phydev->drv->phy_id == PHY_ID_VSC8601) ? MII_VSC8244_IMASK_MASK : MII_VSC8221_IMASK_MASK); else { @@ -271,6 +273,18 @@ static struct phy_driver vsc82xx_driver[] = { .ack_interrupt = &vsc824x_ack_interrupt, .config_intr = &vsc82xx_config_intr, .driver = { .owner = THIS_MODULE,}, +}, { + .phy_id = PHY_ID_VSC8601, + .name = "Vitesse VSC8601", + .phy_id_mask = 0x000ffff0, + .features = PHY_GBIT_FEATURES, + .flags = PHY_HAS_INTERRUPT, + .config_init = &genphy_config_init, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .ack_interrupt = &vsc824x_ack_interrupt, + .config_intr = &vsc82xx_config_intr, + .driver = { .owner = THIS_MODULE,}, }, { .phy_id = PHY_ID_VSC8662, .name = "Vitesse VSC8662", diff --git a/drivers/net/wireless/Kconfig b/drivers/net/wireless/Kconfig index 16604bdf5197e..01155d3bf86d5 100644 --- a/drivers/net/wireless/Kconfig +++ b/drivers/net/wireless/Kconfig @@ -265,6 +265,7 @@ config MWL8K To compile this driver as a module, choose M here: the module will be called mwl8k. If unsure, say N. +source "drivers/net/wireless/acc1340/Kconfig" source "drivers/net/wireless/ath/Kconfig" source "drivers/net/wireless/b43/Kconfig" source "drivers/net/wireless/b43legacy/Kconfig" @@ -277,6 +278,7 @@ source "drivers/net/wireless/libertas/Kconfig" source "drivers/net/wireless/orinoco/Kconfig" source "drivers/net/wireless/p54/Kconfig" source "drivers/net/wireless/rt2x00/Kconfig" +source "drivers/net/wireless/mediatek/Kconfig" source "drivers/net/wireless/rtlwifi/Kconfig" source "drivers/net/wireless/ti/Kconfig" source "drivers/net/wireless/zd1211rw/Kconfig" diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile index 0c88916867187..1d445d3a54e60 100644 --- a/drivers/net/wireless/Makefile +++ b/drivers/net/wireless/Makefile @@ -45,8 +45,12 @@ obj-$(CONFIG_IWLWIFI) += iwlwifi/ obj-$(CONFIG_IWLEGACY) += iwlegacy/ obj-$(CONFIG_RT2X00) += rt2x00/ +obj-$(CONFIG_WL_MEDIATEK) += mediatek/ + obj-$(CONFIG_P54_COMMON) += p54/ +obj-$(CONFIG_ACC1340) += acc1340/ + obj-$(CONFIG_ATH_CARDS) += ath/ obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o diff --git a/drivers/net/wireless/acc1340/Kconfig b/drivers/net/wireless/acc1340/Kconfig new file mode 100644 index 0000000000000..0fa7c5f59c728 --- /dev/null +++ b/drivers/net/wireless/acc1340/Kconfig @@ -0,0 +1,30 @@ +config ACC1340 + tristate "ACC1340 WLAN support" + depends on MAC80211 && CFG80211 + help + This is a driver for the Amp'ed RF ACC1340 WLAN chipsets. + This option just enables the driver core, see below for + specific bus support. + +if ACC1340 + +config ACC1340_WLAN_SDIO + tristate "Support SDIO platforms" + depends on ACC1340 && MMC + help + Enable support for the ACC1340 connected via an SDIO bus. + By default this driver only supports the Sagrad SG901-1091/1098 EVK + and similar designs that utilize a hardware reset circuit. To + support different ACC1340 SDIO designs you will need to override + the default platform data by calling cw1200_sdio_set_platform_data() + in your board setup file. + +config ACC1340_WLAN_SPI + tristate "Support SPI platforms" + depends on ACC1340 && SPI + help + Enables support for the ACC1340 connected via a SPI bus. You will + need to add appropriate platform data glue in your board setup + file. + +endif diff --git a/drivers/net/wireless/acc1340/Makefile b/drivers/net/wireless/acc1340/Makefile new file mode 100644 index 0000000000000..ff234b51f0248 --- /dev/null +++ b/drivers/net/wireless/acc1340/Makefile @@ -0,0 +1,21 @@ +acc1340_core-y := \ + fwio.o \ + txrx.o \ + main.o \ + queue.o \ + hwio.o \ + bh.o \ + wsm.o \ + sta.o \ + scan.o \ + debug.o +acc1340_core-$(CONFIG_PM) += pm.o + +# CFLAGS_sta.o += -DDEBUG + +acc1340_wlan_sdio-y := acc1340_sdio.o +acc1340_wlan_spi-y := acc1340_spi.o + +obj-$(CONFIG_ACC1340) += acc1340_core.o +obj-$(CONFIG_ACC1340_WLAN_SDIO) += acc1340_wlan_sdio.o +obj-$(CONFIG_ACC1340_WLAN_SPI) += acc1340_wlan_spi.o diff --git a/drivers/net/wireless/acc1340/acc1340.h b/drivers/net/wireless/acc1340/acc1340.h new file mode 100644 index 0000000000000..1ad7d36025200 --- /dev/null +++ b/drivers/net/wireless/acc1340/acc1340.h @@ -0,0 +1,323 @@ +/* + * Common private data for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on the mac80211 Prism54 code, which is + * Copyright (c) 2006, Michael Wu + * + * Based on the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * + * 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 CW1200_H +#define CW1200_H + +#include +#include +#include +#include + +#include "queue.h" +#include "wsm.h" +#include "scan.h" +#include "txrx.h" +#include "pm.h" + +/* Forward declarations */ +struct hwbus_ops; +struct task_struct; +struct cw1200_debug_priv; +struct firmware; + +#define CW1200_MAX_CTRL_FRAME_LEN (0x1000) + +#define CW1200_MAX_STA_IN_AP_MODE (5) +#define CW1200_LINK_ID_AFTER_DTIM (CW1200_MAX_STA_IN_AP_MODE + 1) +#define CW1200_LINK_ID_UAPSD (CW1200_MAX_STA_IN_AP_MODE + 2) +#define CW1200_LINK_ID_MAX (CW1200_MAX_STA_IN_AP_MODE + 3) +#define CW1200_MAX_REQUEUE_ATTEMPTS (5) + +#define CW1200_MAX_TID (8) + +#define CW1200_BLOCK_ACK_CNT (30) +#define CW1200_BLOCK_ACK_THLD (800) +#define CW1200_BLOCK_ACK_HIST (3) +#define CW1200_BLOCK_ACK_INTERVAL (1 * HZ / CW1200_BLOCK_ACK_HIST) + +#define CW1200_JOIN_TIMEOUT (1 * HZ) +#define CW1200_AUTH_TIMEOUT (5 * HZ) + +struct cw1200_ht_info { + struct ieee80211_sta_ht_cap ht_cap; + enum nl80211_channel_type channel_type; + u16 operation_mode; +}; + +/* Please keep order */ +enum cw1200_join_status { + CW1200_JOIN_STATUS_PASSIVE = 0, + CW1200_JOIN_STATUS_MONITOR, + CW1200_JOIN_STATUS_JOINING, + CW1200_JOIN_STATUS_PRE_STA, + CW1200_JOIN_STATUS_STA, + CW1200_JOIN_STATUS_IBSS, + CW1200_JOIN_STATUS_AP, +}; + +enum cw1200_link_status { + CW1200_LINK_OFF, + CW1200_LINK_RESERVE, + CW1200_LINK_SOFT, + CW1200_LINK_HARD, + CW1200_LINK_RESET, + CW1200_LINK_RESET_REMAP, +}; + +extern int cw1200_power_mode; +extern const char * const cw1200_fw_types[]; + +struct cw1200_link_entry { + unsigned long timestamp; + enum cw1200_link_status status; + enum cw1200_link_status prev_status; + u8 mac[ETH_ALEN]; + u8 buffered[CW1200_MAX_TID]; + struct sk_buff_head rx_queue; +}; + +struct cw1200_common { + /* interfaces to the rest of the stack */ + struct ieee80211_hw *hw; + struct ieee80211_vif *vif; + struct device *pdev; + + /* Statistics */ + struct ieee80211_low_level_stats stats; + + /* Our macaddr */ + u8 mac_addr[ETH_ALEN]; + + /* Hardware interface */ + const struct hwbus_ops *hwbus_ops; + struct hwbus_priv *hwbus_priv; + + /* Hardware information */ + enum { + HIF_9000_SILICON_VERSATILE = 0, + HIF_8601_VERSATILE, + HIF_8601_SILICON, + } hw_type; + enum { + CW1200_HW_REV_CUT10 = 10, + CW1200_HW_REV_CUT11 = 11, + CW1200_HW_REV_CUT20 = 20, + CW1200_HW_REV_CUT22 = 22, + CW1X60_HW_REV = 40, + } hw_revision; + int hw_refclk; + bool hw_have_5ghz; + const struct firmware *sdd; + char *sdd_path; + + struct cw1200_debug_priv *debug; + + struct workqueue_struct *workqueue; + struct mutex conf_mutex; + + struct cw1200_queue tx_queue[4]; + struct cw1200_queue_stats tx_queue_stats; + int tx_burst_idx; + + /* firmware/hardware info */ + unsigned int tx_hdr_len; + + /* Radio data */ + int output_power; + + /* BBP/MAC state */ + struct ieee80211_rate *rates; + struct ieee80211_rate *mcs_rates; + struct ieee80211_channel *channel; + struct wsm_edca_params edca; + struct wsm_tx_queue_params tx_queue_params; + struct wsm_mib_association_mode association_mode; + struct wsm_set_bss_params bss_params; + struct cw1200_ht_info ht_info; + struct wsm_set_pm powersave_mode; + struct wsm_set_pm firmware_ps_mode; + int cqm_rssi_thold; + unsigned cqm_rssi_hyst; + bool cqm_use_rssi; + int cqm_beacon_loss_count; + int channel_switch_in_progress; + wait_queue_head_t channel_switch_done; + u8 long_frame_max_tx_count; + u8 short_frame_max_tx_count; + int mode; + bool enable_beacon; + int beacon_int; + bool listening; + struct wsm_rx_filter rx_filter; + struct wsm_mib_multicast_filter multicast_filter; + bool has_multicast_subscription; + bool disable_beacon_filter; + struct work_struct update_filtering_work; + struct work_struct set_beacon_wakeup_period_work; + + u8 ba_rx_tid_mask; + u8 ba_tx_tid_mask; + + struct cw1200_pm_state pm_state; + + struct wsm_p2p_ps_modeinfo p2p_ps_modeinfo; + struct wsm_uapsd_info uapsd_info; + bool setbssparams_done; + bool bt_present; + u8 conf_listen_interval; + u32 listen_interval; + u32 erp_info; + u32 rts_threshold; + + /* BH */ + atomic_t bh_rx; + atomic_t bh_tx; + atomic_t bh_term; + atomic_t bh_suspend; + + struct workqueue_struct *bh_workqueue; + struct work_struct bh_work; + + int bh_error; + wait_queue_head_t bh_wq; + wait_queue_head_t bh_evt_wq; + u8 buf_id_tx; + u8 buf_id_rx; + u8 wsm_rx_seq; + u8 wsm_tx_seq; + int hw_bufs_used; + bool powersave_enabled; + bool device_can_sleep; + + /* Scan status */ + struct cw1200_scan scan; + /* Keep cw1200 awake (WUP = 1) 1 second after each scan to avoid + * FW issue with sleeping/waking up. + */ + atomic_t recent_scan; + struct delayed_work clear_recent_scan_work; + + /* WSM */ + struct wsm_startup_ind wsm_caps; + struct mutex wsm_cmd_mux; + struct wsm_buf wsm_cmd_buf; + struct wsm_cmd wsm_cmd; + wait_queue_head_t wsm_cmd_wq; + wait_queue_head_t wsm_startup_done; + int firmware_ready; + atomic_t tx_lock; + + /* WSM debug */ + int wsm_enable_wsm_dumps; + + /* WSM Join */ + enum cw1200_join_status join_status; + u32 pending_frame_id; + bool join_pending; + struct delayed_work join_timeout; + struct work_struct unjoin_work; + struct work_struct join_complete_work; + int join_complete_status; + int join_dtim_period; + bool delayed_unjoin; + + /* TX/RX and security */ + s8 wep_default_key_id; + struct work_struct wep_key_work; + u32 key_map; + struct wsm_add_key keys[WSM_KEY_MAX_INDEX + 1]; + + /* AP powersave */ + u32 link_id_map; + struct cw1200_link_entry link_id_db[CW1200_MAX_STA_IN_AP_MODE]; + struct work_struct link_id_work; + struct delayed_work link_id_gc_work; + u32 sta_asleep_mask; + u32 pspoll_mask; + bool aid0_bit_set; + spinlock_t ps_state_lock; /* Protect power save state */ + bool buffered_multicasts; + bool tx_multicast; + struct work_struct set_tim_work; + struct work_struct set_cts_work; + struct work_struct multicast_start_work; + struct work_struct multicast_stop_work; + struct timer_list mcast_timeout; + + /* WSM events and CQM implementation */ + spinlock_t event_queue_lock; /* Protect event queue */ + struct list_head event_queue; + struct work_struct event_handler; + + struct delayed_work bss_loss_work; + spinlock_t bss_loss_lock; /* Protect BSS loss state */ + int bss_loss_state; + u32 bss_loss_confirm_id; + int delayed_link_loss; + struct work_struct bss_params_work; + + /* TX rate policy cache */ + struct tx_policy_cache tx_policy_cache; + struct work_struct tx_policy_upload_work; + + /* legacy PS mode switch in suspend */ + int ps_mode_switch_in_progress; + wait_queue_head_t ps_mode_switch_done; + + /* Workaround for WFD testcase 6.1.10*/ + struct work_struct linkid_reset_work; + u8 action_frame_sa[ETH_ALEN]; + u8 action_linkid; +}; + +struct cw1200_sta_priv { + int link_id; +}; + +/* interfaces for the drivers */ +int cw1200_core_probe(const struct hwbus_ops *hwbus_ops, + struct hwbus_priv *hwbus, + struct device *pdev, + struct cw1200_common **pself, + int ref_clk, const u8 *macaddr, + const char *sdd_path, bool have_5ghz); +void cw1200_core_release(struct cw1200_common *self); + +#define FWLOAD_BLOCK_SIZE (1024) + +static inline int cw1200_is_ht(const struct cw1200_ht_info *ht_info) +{ + return ht_info->channel_type != NL80211_CHAN_NO_HT; +} + +static inline int cw1200_ht_greenfield(const struct cw1200_ht_info *ht_info) +{ + return cw1200_is_ht(ht_info) && + (ht_info->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) && + !(ht_info->operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); +} + +static inline int cw1200_ht_ampdu_density(const struct cw1200_ht_info *ht_info) +{ + if (!cw1200_is_ht(ht_info)) + return 0; + return ht_info->ht_cap.ampdu_density; +} + +#endif /* CW1200_H */ diff --git a/drivers/net/wireless/acc1340/acc1340_sdio.c b/drivers/net/wireless/acc1340/acc1340_sdio.c new file mode 100644 index 0000000000000..ba35daec0a3a0 --- /dev/null +++ b/drivers/net/wireless/acc1340/acc1340_sdio.c @@ -0,0 +1,427 @@ +/* + * Mac80211 SDIO driver for ST-Ericsson CW1200 device + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "hwbus.h" +#include +#include "hwio.h" + +MODULE_AUTHOR("Dmitry Tarnyagin "); +MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SDIO driver"); +MODULE_LICENSE("GPL"); + +#define SDIO_BLOCK_SIZE (512) + +/* Default platform data for Sagrad modules */ +static struct cw1200_platform_data_sdio sagrad_109x_evk_platform_data = { + .ref_clk = 26000, + .have_5ghz = false, + .sdd_file = "sdd_6010.bin", + /*85 is irq id got from GPIO1_28pin "gpio_to_irq(60)"*/ + .irq = 85 +}; + +/* Allow platform data to be overridden */ +static struct cw1200_platform_data_sdio *global_plat_data = &sagrad_109x_evk_platform_data; + +void __init acc1340_sdio_set_platform_data(struct cw1200_platform_data_sdio *pdata) +{ + global_plat_data = pdata; +} + +struct hwbus_priv { + struct sdio_func *func; + struct cw1200_common *core; + const struct cw1200_platform_data_sdio *pdata; +}; + +#ifndef SDIO_VENDOR_ID_STE +#define SDIO_VENDOR_ID_STE 0x0020 +#endif + +#ifndef SDIO_DEVICE_ID_STE_CW1200 +#define SDIO_DEVICE_ID_STE_CW1200 0x2280 +#endif + +static const struct sdio_device_id cw1200_sdio_ids[] = { + {SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID)}, + { /* end: all zeroes */ }, +}; + +/* hwbus_ops implemetation */ + +static int cw1200_sdio_memcpy_fromio(struct hwbus_priv *self, + unsigned int addr, + void *dst, int count) +{ + return sdio_memcpy_fromio(self->func, dst, addr, count); +} + +static int cw1200_sdio_memcpy_toio(struct hwbus_priv *self, + unsigned int addr, + const void *src, int count) +{ + return sdio_memcpy_toio(self->func, addr, (void *)src, count); +} + +static void cw1200_sdio_lock(struct hwbus_priv *self) +{ + sdio_claim_host(self->func); +} + +static void cw1200_sdio_unlock(struct hwbus_priv *self) +{ + sdio_release_host(self->func); +} + +static void cw1200_sdio_irq_handler(struct sdio_func *func) +{ + struct hwbus_priv *self = sdio_get_drvdata(func); + + /* note: sdio_host already claimed here. */ + if (self->core) + cw1200_irq_handler(self->core); +} + +static irqreturn_t cw1200_gpio_hardirq(int irq, void *dev_id) +{ + return IRQ_WAKE_THREAD; +} + +static irqreturn_t cw1200_gpio_irq(int irq, void *dev_id) +{ + struct hwbus_priv *self = dev_id; + + if (self->core) { + cw1200_sdio_lock(self); + cw1200_irq_handler(self->core); + cw1200_sdio_unlock(self); + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +static int cw1200_request_irq(struct hwbus_priv *self) +{ + int ret; + u8 cccr; + + cccr = sdio_f0_readb(self->func, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto err; + + /* Master interrupt enable ... */ + cccr |= BIT(0); + + /* ... for our function */ + cccr |= BIT(self->func->num); + + sdio_f0_writeb(self->func, cccr, SDIO_CCCR_IENx, &ret); + if (WARN_ON(ret)) + goto err; + + ret = enable_irq_wake(self->pdata->irq); + if (WARN_ON(ret)) + goto err; + + /* Request the IRQ */ + ret = request_threaded_irq(self->pdata->irq, cw1200_gpio_hardirq, + cw1200_gpio_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "cw1200_wlan_irq", self); + if (WARN_ON(ret)) + goto err; + + return 0; + +err: + return ret; +} + +static int cw1200_sdio_irq_subscribe(struct hwbus_priv *self) +{ + int ret = 0; + + pr_debug("SW IRQ subscribe\n"); + sdio_claim_host(self->func); + if (self->pdata->irq) + ret = cw1200_request_irq(self); + else + ret = sdio_claim_irq(self->func, cw1200_sdio_irq_handler); + + sdio_release_host(self->func); + return ret; +} + +static int cw1200_sdio_irq_unsubscribe(struct hwbus_priv *self) +{ + int ret = 0; + + pr_debug("SW IRQ unsubscribe\n"); + + if (self->pdata->irq) { + disable_irq_wake(self->pdata->irq); + free_irq(self->pdata->irq, self); + } else { + sdio_claim_host(self->func); + ret = sdio_release_irq(self->func); + sdio_release_host(self->func); + } + return ret; +} + +static int cw1200_sdio_off(const struct cw1200_platform_data_sdio *pdata) +{ + if (pdata->reset) { + gpio_set_value(pdata->reset, 0); + msleep(30); /* Min is 2 * CLK32K cycles */ + gpio_free(pdata->reset); + } + + if (pdata->power_ctrl) + pdata->power_ctrl(pdata, false); + if (pdata->clk_ctrl) + pdata->clk_ctrl(pdata, false); + + return 0; +} + +static int cw1200_sdio_on(const struct cw1200_platform_data_sdio *pdata) +{ + /* Ensure I/Os are pulled low */ + if (pdata->reset) { + gpio_request(pdata->reset, "cw1200_wlan_reset"); + gpio_direction_output(pdata->reset, 0); + } + if (pdata->powerup) { + gpio_request(pdata->powerup, "cw1200_wlan_powerup"); + gpio_direction_output(pdata->powerup, 0); + } + if (pdata->reset || pdata->powerup) + msleep(10); /* Settle time? */ + + /* Enable 3v3 and 1v8 to hardware */ + if (pdata->power_ctrl) { + if (pdata->power_ctrl(pdata, true)) { + pr_err("power_ctrl() failed!\n"); + return -1; + } + } + + /* Enable CLK32K */ + if (pdata->clk_ctrl) { + if (pdata->clk_ctrl(pdata, true)) { + pr_err("clk_ctrl() failed!\n"); + return -1; + } + msleep(10); /* Delay until clock is stable for 2 cycles */ + } + + /* Enable POWERUP signal */ + if (pdata->powerup) { + gpio_set_value(pdata->powerup, 1); + msleep(250); /* or more..? */ + } + /* Enable RSTn signal */ + if (pdata->reset) { + gpio_set_value(pdata->reset, 1); + msleep(50); /* Or more..? */ + } + return 0; +} + +static size_t cw1200_sdio_align_size(struct hwbus_priv *self, size_t size) +{ + if (self->pdata->no_nptb) + size = round_up(size, SDIO_BLOCK_SIZE); + else + size = sdio_align_size(self->func, size); + + return size; +} + +static int cw1200_sdio_pm(struct hwbus_priv *self, bool suspend) +{ + int ret = 0; + + if (self->pdata->irq) + ret = irq_set_irq_wake(self->pdata->irq, suspend); + return ret; +} + +static struct hwbus_ops cw1200_sdio_hwbus_ops = { + .hwbus_memcpy_fromio = cw1200_sdio_memcpy_fromio, + .hwbus_memcpy_toio = cw1200_sdio_memcpy_toio, + .lock = cw1200_sdio_lock, + .unlock = cw1200_sdio_unlock, + .align_size = cw1200_sdio_align_size, + .power_mgmt = cw1200_sdio_pm, +}; + +/* Probe Function to be called by SDIO stack when device is discovered */ +static int cw1200_sdio_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct hwbus_priv *self; + int status; + + pr_info("cw1200_wlan_sdio: Probe called\n"); + + /* We are only able to handle the wlan function */ + if (func->num != 0x01) + return -ENODEV; + + self = kzalloc(sizeof(*self), GFP_KERNEL); + if (!self) { + pr_err("Can't allocate SDIO hwbus_priv.\n"); + return -ENOMEM; + } + + func->card->quirks |= MMC_QUIRK_LENIENT_FN0; + + self->pdata = global_plat_data; /* FIXME */ + self->func = func; + sdio_set_drvdata(func, self); + sdio_claim_host(func); + sdio_enable_func(func); + sdio_release_host(func); + + status = cw1200_sdio_irq_subscribe(self); + + status = cw1200_core_probe(&cw1200_sdio_hwbus_ops, + self, &func->dev, &self->core, + self->pdata->ref_clk, + self->pdata->macaddr, + self->pdata->sdd_file, + self->pdata->have_5ghz); + if (status) { + cw1200_sdio_irq_unsubscribe(self); + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + sdio_set_drvdata(func, NULL); + kfree(self); + } + + return status; +} + +/* Disconnect Function to be called by SDIO stack when + * device is disconnected + */ +static void cw1200_sdio_disconnect(struct sdio_func *func) +{ + struct hwbus_priv *self = sdio_get_drvdata(func); + + if (self) { + cw1200_sdio_irq_unsubscribe(self); + if (self->core) { + cw1200_core_release(self->core); + self->core = NULL; + } + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + sdio_set_drvdata(func, NULL); + kfree(self); + } +} + +#ifdef CONFIG_PM +static int cw1200_sdio_suspend(struct device *dev) +{ + int ret; + struct sdio_func *func = dev_to_sdio_func(dev); + struct hwbus_priv *self = sdio_get_drvdata(func); + + if (!cw1200_can_suspend(self->core)) + return -EAGAIN; + + /* Notify SDIO that CW1200 will remain powered during suspend */ + ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); + if (ret) + pr_err("Error setting SDIO pm flags: %i\n", ret); + + return ret; +} + +static int cw1200_sdio_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops cw1200_pm_ops = { + .suspend = cw1200_sdio_suspend, + .resume = cw1200_sdio_resume, +}; +#endif + +static struct sdio_driver sdio_driver = { + .name = "cw1200_wlan_sdio", + .id_table = cw1200_sdio_ids, + .probe = cw1200_sdio_probe, + .remove = cw1200_sdio_disconnect, +#ifdef CONFIG_PM + .drv = { + .pm = &cw1200_pm_ops, + } +#endif +}; + +/* Init Module function -> Called by insmod */ +static int __init cw1200_sdio_init(void) +{ + const struct cw1200_platform_data_sdio *pdata; + int ret; + + /* FIXME -- this won't support multiple devices */ + pdata = global_plat_data; + + if (cw1200_sdio_on(pdata)) { + ret = -1; + goto err; + } + + ret = sdio_register_driver(&sdio_driver); + if (ret) + goto err; + + return 0; + +err: + cw1200_sdio_off(pdata); + return ret; +} + +/* Called at Driver Unloading */ +static void __exit cw1200_sdio_exit(void) +{ + const struct cw1200_platform_data_sdio *pdata; + + /* FIXME -- this won't support multiple devices */ + pdata = global_plat_data; + sdio_unregister_driver(&sdio_driver); + cw1200_sdio_off(pdata); +} + + +module_init(cw1200_sdio_init); +module_exit(cw1200_sdio_exit); diff --git a/drivers/net/wireless/acc1340/acc1340_spi.c b/drivers/net/wireless/acc1340/acc1340_spi.c new file mode 100644 index 0000000000000..eb638d16f6b40 --- /dev/null +++ b/drivers/net/wireless/acc1340/acc1340_spi.c @@ -0,0 +1,493 @@ +/* + * Mac80211 SPI driver for ST-Ericsson CW1200 device + * + * Copyright (c) 2011, Sagrad Inc. + * Author: Solomon Peachy + * + * Based on cw1200_sdio.c + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "hwbus.h" +#include +#include "hwio.h" + +MODULE_AUTHOR("Solomon Peachy "); +MODULE_DESCRIPTION("mac80211 ST-Ericsson CW1200 SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:acc1340_wlan_spi"); + +/* #define SPI_DEBUG */ + +struct hwbus_priv { + struct spi_device *func; + struct cw1200_common *core; + const struct cw1200_platform_data_spi *pdata; + spinlock_t lock; /* Serialize all bus operations */ + wait_queue_head_t wq; + int claimed; +}; + +#define SDIO_TO_SPI_ADDR(addr) ((addr & 0x1f)>>2) +#define SET_WRITE 0x7FFF /* usage: and operation */ +#define SET_READ 0x8000 /* usage: or operation */ + +/* Notes on byte ordering: + LE: B0 B1 B2 B3 + BE: B3 B2 B1 B0 + + Hardware expects 32-bit data to be written as 16-bit BE words: + + B1 B0 B3 B2 +*/ + +static int cw1200_spi_memcpy_fromio(struct hwbus_priv *self, + unsigned int addr, + void *dst, int count) +{ + int ret, i; + u16 regaddr; + struct spi_message m; + + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .rx_buf = dst, + .len = count, + }; + + regaddr = (SDIO_TO_SPI_ADDR(addr))<<12; + regaddr |= SET_READ; + regaddr |= (count>>1); + +#ifdef SPI_DEBUG + pr_info("READ : %04d from 0x%02x (%04x)\n", count, addr, regaddr); +#endif + + /* Header is LE16 */ + regaddr = cpu_to_le16(regaddr); + + /* We have to byteswap if the SPI bus is limited to 8b operation + or we are running on a Big Endian system + */ +#if defined(__LITTLE_ENDIAN) + if (self->func->bits_per_word == 8) +#endif + regaddr = swab16(regaddr); + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + ret = spi_sync(self->func, &m); + +#ifdef SPI_DEBUG + pr_info("READ : "); + for (i = 0; i < t_addr.len; i++) + printk("%02x ", ((u8 *)t_addr.tx_buf)[i]); + printk(" : "); + for (i = 0; i < t_msg.len; i++) + printk("%02x ", ((u8 *)t_msg.rx_buf)[i]); + printk("\n"); +#endif + + /* We have to byteswap if the SPI bus is limited to 8b operation + or we are running on a Big Endian system + */ +#if defined(__LITTLE_ENDIAN) + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t *)dst; + for (i = 0; i < ((count + 1) >> 1); i++) + buf[i] = swab16(buf[i]); + } + + return ret; +} + +static int cw1200_spi_memcpy_toio(struct hwbus_priv *self, + unsigned int addr, + const void *src, int count) +{ + int rval, i; + u16 regaddr; + struct spi_transfer t_addr = { + .tx_buf = ®addr, + .len = sizeof(regaddr), + }; + struct spi_transfer t_msg = { + .tx_buf = src, + .len = count, + }; + struct spi_message m; + + regaddr = (SDIO_TO_SPI_ADDR(addr))<<12; + regaddr &= SET_WRITE; + regaddr |= (count>>1); + +#ifdef SPI_DEBUG + pr_info("WRITE: %04d to 0x%02x (%04x)\n", count, addr, regaddr); +#endif + + /* Header is LE16 */ + regaddr = cpu_to_le16(regaddr); + + /* We have to byteswap if the SPI bus is limited to 8b operation + or we are running on a Big Endian system + */ +#if defined(__LITTLE_ENDIAN) + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t *)src; + regaddr = swab16(regaddr); + for (i = 0; i < ((count + 1) >> 1); i++) + buf[i] = swab16(buf[i]); + } + +#ifdef SPI_DEBUG + pr_info("WRITE: "); + for (i = 0; i < t_addr.len; i++) + printk("%02x ", ((u8 *)t_addr.tx_buf)[i]); + printk(" : "); + for (i = 0; i < t_msg.len; i++) + printk("%02x ", ((u8 *)t_msg.tx_buf)[i]); + printk("\n"); +#endif + + spi_message_init(&m); + spi_message_add_tail(&t_addr, &m); + spi_message_add_tail(&t_msg, &m); + rval = spi_sync(self->func, &m); + +#ifdef SPI_DEBUG + pr_info("WROTE: %d\n", m.actual_length); +#endif + +#if defined(__LITTLE_ENDIAN) + /* We have to byteswap if the SPI bus is limited to 8b operation */ + if (self->func->bits_per_word == 8) +#endif + { + uint16_t *buf = (uint16_t *)src; + for (i = 0; i < ((count + 1) >> 1); i++) + buf[i] = swab16(buf[i]); + } + return rval; +} + +static void cw1200_spi_lock(struct hwbus_priv *self) +{ + unsigned long flags; + + DECLARE_WAITQUEUE(wait, current); + + might_sleep(); + + add_wait_queue(&self->wq, &wait); + spin_lock_irqsave(&self->lock, flags); + while (1) { + set_current_state(TASK_UNINTERRUPTIBLE); + if (!self->claimed) + break; + spin_unlock_irqrestore(&self->lock, flags); + schedule(); + spin_lock_irqsave(&self->lock, flags); + } + set_current_state(TASK_RUNNING); + self->claimed = 1; + spin_unlock_irqrestore(&self->lock, flags); + remove_wait_queue(&self->wq, &wait); + + return; +} + +static void cw1200_spi_unlock(struct hwbus_priv *self) +{ + unsigned long flags; + + spin_lock_irqsave(&self->lock, flags); + self->claimed = 0; + spin_unlock_irqrestore(&self->lock, flags); + wake_up(&self->wq); + + return; +} + +static irqreturn_t cw1200_spi_irq_handler(int irq, void *dev_id) +{ + struct hwbus_priv *self = dev_id; + + if (self->core) { + cw1200_spi_lock(self); + cw1200_irq_handler(self->core); + cw1200_spi_unlock(self); + return IRQ_HANDLED; + } else { + return IRQ_NONE; + } +} + +static int cw1200_spi_irq_subscribe(struct hwbus_priv *self) +{ + int ret; + unsigned int irq = self->func->irq; + + pr_debug("SW IRQ subscribe\n"); + + ret = request_threaded_irq(irq, NULL, + cw1200_spi_irq_handler, + IRQF_TRIGGER_RISING | IRQF_SHARED | IRQF_ONESHOT, + "cw1200_wlan_irq", self); + if (WARN_ON(ret < 0)) + goto exit; + + ret = enable_irq_wake(irq); + if (WARN_ON(ret)) + goto free_irq; + + return 0; + +free_irq: + free_irq(irq, self); +exit: + return ret; +} + +static int cw1200_spi_irq_unsubscribe(struct hwbus_priv *self) +{ + int ret = 0; + + pr_debug("SW IRQ unsubscribe\n"); + disable_irq_wake(self->func->irq); + free_irq(self->func->irq, self); + + return ret; +} + +static int cw1200_spi_off(const struct cw1200_platform_data_spi *pdata) +{ + if (pdata->reset) { + gpio_set_value(pdata->reset, 0); + msleep(30); /* Min is 2 * CLK32K cycles */ + gpio_free(pdata->reset); + } + + if (pdata->power_ctrl) + pdata->power_ctrl(pdata, false); + if (pdata->clk_ctrl) + pdata->clk_ctrl(pdata, false); + + return 0; +} + +static int cw1200_spi_on(const struct cw1200_platform_data_spi *pdata) +{ + /* Ensure I/Os are pulled low */ + if (pdata->reset) { + gpio_request(pdata->reset, "cw1200_wlan_reset"); + gpio_direction_output(pdata->reset, 0); + } + if (pdata->powerup) { + gpio_request(pdata->powerup, "cw1200_wlan_powerup"); + gpio_direction_output(pdata->powerup, 0); + } + if (pdata->reset || pdata->powerup) + msleep(10); /* Settle time? */ + + /* Enable 3v3 and 1v8 to hardware */ + if (pdata->power_ctrl) { + if (pdata->power_ctrl(pdata, true)) { + pr_err("power_ctrl() failed!\n"); + return -1; + } + } + + /* Enable CLK32K */ + if (pdata->clk_ctrl) { + if (pdata->clk_ctrl(pdata, true)) { + pr_err("clk_ctrl() failed!\n"); + return -1; + } + msleep(10); /* Delay until clock is stable for 2 cycles */ + } + + /* Enable POWERUP signal */ + if (pdata->powerup) { + gpio_set_value(pdata->powerup, 1); + msleep(250); /* or more..? */ + } + /* Enable RSTn signal */ + if (pdata->reset) { + gpio_set_value(pdata->reset, 1); + msleep(50); /* Or more..? */ + } + return 0; +} + +static size_t cw1200_spi_align_size(struct hwbus_priv *self, size_t size) +{ + return size & 1 ? size + 1 : size; +} + +static int cw1200_spi_pm(struct hwbus_priv *self, bool suspend) +{ + return irq_set_irq_wake(self->func->irq, suspend); +} + +static struct hwbus_ops cw1200_spi_hwbus_ops = { + .hwbus_memcpy_fromio = cw1200_spi_memcpy_fromio, + .hwbus_memcpy_toio = cw1200_spi_memcpy_toio, + .lock = cw1200_spi_lock, + .unlock = cw1200_spi_unlock, + .align_size = cw1200_spi_align_size, + .power_mgmt = cw1200_spi_pm, +}; + +static const struct cw1200_platform_data_spi spi_platform_data = { + .spi_bits_per_word = 16, + .ref_clk = 26000, + .have_5ghz = false, + //.sdd_file = "sdd_6010.bin", +}; + +/* Probe Function to be called by SPI stack when device is discovered */ +static int cw1200_spi_probe(struct spi_device *func) +{ + const struct cw1200_platform_data_spi *plat_data = &spi_platform_data; + struct hwbus_priv *self; + int status; + + /* Sanity check speed */ + if (func->max_speed_hz > 52000000) + func->max_speed_hz = 52000000; + if (func->max_speed_hz < 1000000) + func->max_speed_hz = 1000000; + + /* Fix up transfer size */ + if (plat_data->spi_bits_per_word) + func->bits_per_word = plat_data->spi_bits_per_word; + if (!func->bits_per_word) + func->bits_per_word = 16; + + /* And finally.. */ + func->mode = SPI_MODE_3; + /* Just make control max_speed for black_beagle_board */ + func->max_speed_hz = 24000000; + + pr_info("acc1340_wlan_spi: Probe called (CS %d M %d BPW %d CLK %d)\n", + func->chip_select, func->mode, func->bits_per_word, + func->max_speed_hz); + + if (cw1200_spi_on(plat_data)) { + pr_err("spi_on() failed!\n"); + return -1; + } + + if (spi_setup(func)) { + pr_err("spi_setup() failed!\n"); + return -1; + } + + self = devm_kzalloc(&func->dev, sizeof(*self), GFP_KERNEL); + if (!self) { + pr_err("Can't allocate SPI hwbus_priv."); + return -ENOMEM; + } + + self->pdata = plat_data; + self->func = func; + spin_lock_init(&self->lock); + + spi_set_drvdata(func, self); + + init_waitqueue_head(&self->wq); + + + if (!self->func->irq) { + /* set GPIO1 28 pin as interrupt input */ + self->func->irq = gpio_to_irq(60); + } + + status = cw1200_spi_irq_subscribe(self); + + status = cw1200_core_probe(&cw1200_spi_hwbus_ops, + self, &func->dev, &self->core, + self->pdata->ref_clk, + self->pdata->macaddr, + self->pdata->sdd_file, + self->pdata->have_5ghz); + + if (status) { + cw1200_spi_irq_unsubscribe(self); + cw1200_spi_off(plat_data); + } + + return status; +} + +/* Disconnect Function to be called by SPI stack when device is disconnected */ +static int cw1200_spi_disconnect(struct spi_device *func) +{ + struct hwbus_priv *self = spi_get_drvdata(func); + + if (self) { + cw1200_spi_irq_unsubscribe(self); + if (self->core) { + cw1200_core_release(self->core); + self->core = NULL; + } + } + cw1200_spi_off(self->pdata); + + return 0; +} + +#ifdef CONFIG_PM +static int cw1200_spi_suspend(struct device *dev) +{ + struct hwbus_priv *self = spi_get_drvdata(to_spi_device(dev)); + + if (!cw1200_can_suspend(self->core)) + return -EAGAIN; + + /* XXX notify host that we have to keep CW1200 powered on? */ + return 0; +} + +static SIMPLE_DEV_PM_OPS(cw1200_pm_ops, cw1200_spi_suspend, NULL); + +#endif + +static struct spi_driver spi_driver = { + .probe = cw1200_spi_probe, + .remove = cw1200_spi_disconnect, + .driver = { + .name = "acc1340_wlan_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &cw1200_pm_ops, +#endif + }, +}; + +module_spi_driver(spi_driver); diff --git a/drivers/net/wireless/acc1340/bh.c b/drivers/net/wireless/acc1340/bh.c new file mode 100644 index 0000000000000..4691cc11e6e12 --- /dev/null +++ b/drivers/net/wireless/acc1340/bh.c @@ -0,0 +1,619 @@ +/* + * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * ST-Ericsson UMAC CW1200 driver, which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh + * + * 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 "acc1340.h" +#include "bh.h" +#include "hwio.h" +#include "wsm.h" +#include "hwbus.h" +#include "debug.h" +#include "fwio.h" + +static int cw1200_bh(void *arg); + +#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4) +/* an SPI message cannot be bigger than (2"12-1)*2 bytes + * "*2" to cvt to bytes + */ +#define MAX_SZ_RD_WR_BUFFERS (DOWNLOAD_BLOCK_SIZE_WR*2) +#define PIGGYBACK_CTRL_REG (2) +#define EFFECTIVE_BUF_SIZE (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG) + +/* Suspend state privates */ +enum cw1200_bh_pm_state { + CW1200_BH_RESUMED = 0, + CW1200_BH_SUSPEND, + CW1200_BH_SUSPENDED, + CW1200_BH_RESUME, +}; + +typedef int (*cw1200_wsm_handler)(struct cw1200_common *priv, + u8 *data, size_t size); + +static void cw1200_bh_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, bh_work); + cw1200_bh(priv); +} + +int cw1200_register_bh(struct cw1200_common *priv) +{ + int err = 0; + /* Realtime workqueue */ + priv->bh_workqueue = alloc_workqueue("cw1200_bh", + WQ_MEM_RECLAIM | WQ_HIGHPRI + | WQ_CPU_INTENSIVE, 1); + + if (!priv->bh_workqueue) + return -ENOMEM; + + INIT_WORK(&priv->bh_work, cw1200_bh_work); + + pr_debug("[BH] register.\n"); + + atomic_set(&priv->bh_rx, 0); + atomic_set(&priv->bh_tx, 0); + atomic_set(&priv->bh_term, 0); + atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); + priv->bh_error = 0; + priv->hw_bufs_used = 0; + priv->buf_id_tx = 0; + priv->buf_id_rx = 0; + init_waitqueue_head(&priv->bh_wq); + init_waitqueue_head(&priv->bh_evt_wq); + + err = !queue_work(priv->bh_workqueue, &priv->bh_work); + WARN_ON(err); + return err; +} + +void cw1200_unregister_bh(struct cw1200_common *priv) +{ + atomic_add(1, &priv->bh_term); + wake_up(&priv->bh_wq); + + flush_workqueue(priv->bh_workqueue); + + destroy_workqueue(priv->bh_workqueue); + priv->bh_workqueue = NULL; + + pr_debug("[BH] unregistered.\n"); +} + +void cw1200_irq_handler(struct cw1200_common *priv) +{ + pr_debug("[BH] irq.\n"); + + /* Disable Interrupts! */ + /* NOTE: hwbus_ops->lock already held */ + __cw1200_irq_enable(priv, 0); + + if (/* WARN_ON */(priv->bh_error)) + return; + + if (atomic_add_return(1, &priv->bh_rx) == 1) + wake_up(&priv->bh_wq); +} +EXPORT_SYMBOL_GPL(cw1200_irq_handler); + +void cw1200_bh_wakeup(struct cw1200_common *priv) +{ + pr_debug("[BH] wakeup.\n"); + if (priv->bh_error) { + pr_err("[BH] wakeup failed (BH error)\n"); + return; + } + + if (atomic_add_return(1, &priv->bh_tx) == 1) + wake_up(&priv->bh_wq); +} + +int cw1200_bh_suspend(struct cw1200_common *priv) +{ + pr_debug("[BH] suspend.\n"); + if (priv->bh_error) { + wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n"); + return -EINVAL; + } + + atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND); + wake_up(&priv->bh_wq); + return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || + (CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)), + 1 * HZ) ? 0 : -ETIMEDOUT; +} + +int cw1200_bh_resume(struct cw1200_common *priv) +{ + pr_debug("[BH] resume.\n"); + if (priv->bh_error) { + wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n"); + return -EINVAL; + } + + atomic_set(&priv->bh_suspend, CW1200_BH_RESUME); + wake_up(&priv->bh_wq); + return wait_event_timeout(priv->bh_evt_wq, priv->bh_error || + (CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)), + 1 * HZ) ? 0 : -ETIMEDOUT; +} + +static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv) +{ + ++priv->hw_bufs_used; +} + +int wsm_release_tx_buffer(struct cw1200_common *priv, int count) +{ + int ret = 0; + int hw_bufs_used = priv->hw_bufs_used; + + priv->hw_bufs_used -= count; + if (WARN_ON(priv->hw_bufs_used < 0)) + ret = -1; + else if (hw_bufs_used >= priv->wsm_caps.input_buffers) + ret = 1; + if (!priv->hw_bufs_used) + wake_up(&priv->bh_evt_wq); + return ret; +} + +static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv, + u16 *ctrl_reg) +{ + int ret; + + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, ctrl_reg); + if (ret) { + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, ctrl_reg); + if (ret) + pr_err("[BH] Failed to read control register.\n"); + } + + return ret; +} + +static int cw1200_device_wakeup(struct cw1200_common *priv) +{ + u16 ctrl_reg; + int ret; + + pr_debug("[BH] Device wakeup.\n"); + + /* First, set the dpll register */ + ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID, + cw1200_dpll_from_clk(priv->hw_refclk)); + if (WARN_ON(ret)) + return ret; + + /* To force the device to be always-on, the host sets WLAN_UP to 1 */ + ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, + ST90TDS_CONT_WUP_BIT); + if (WARN_ON(ret)) + return ret; + + ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg); + if (WARN_ON(ret)) + return ret; + + /* If the device returns WLAN_RDY as 1, the device is active and will + * remain active. + */ + if (ctrl_reg & ST90TDS_CONT_RDY_BIT) { + pr_debug("[BH] Device awake.\n"); + return 1; + } + + return 0; +} + +/* Must be called from BH thraed. */ +void cw1200_enable_powersave(struct cw1200_common *priv, + bool enable) +{ + pr_debug("[BH] Powerave is %s.\n", + enable ? "enabled" : "disabled"); + priv->powersave_enabled = enable; +} + +static int cw1200_bh_rx_helper(struct cw1200_common *priv, + uint16_t *ctrl_reg, + int *tx) +{ + size_t read_len = 0; + struct sk_buff *skb_rx = NULL; + struct wsm_hdr *wsm; + size_t wsm_len; + u16 wsm_id; + u8 wsm_seq; + int rx_resync = 1; + + size_t alloc_len; + u8 *data; + + read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2; + if (!read_len) + return 0; /* No more work */ + + if (WARN_ON((read_len < sizeof(struct wsm_hdr)) || + (read_len > EFFECTIVE_BUF_SIZE))) { + pr_debug("Invalid read len: %zu (%04x)", + read_len, *ctrl_reg); + goto err; + } + + /* Add SIZE of PIGGYBACK reg (CONTROL Reg) + * to the NEXT Message length + 2 Bytes for SKB + */ + read_len = read_len + 2; + + alloc_len = priv->hwbus_ops->align_size( + priv->hwbus_priv, read_len); + + /* Check if not exceeding CW1200 capabilities */ + if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) { + pr_debug("Read aligned len: %zu\n", + alloc_len); + } + + skb_rx = dev_alloc_skb(alloc_len); + if (WARN_ON(!skb_rx)) + goto err; + + skb_trim(skb_rx, 0); + skb_put(skb_rx, read_len); + data = skb_rx->data; + if (WARN_ON(!data)) + goto err; + + if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) { + pr_err("rx blew up, len %zu\n", alloc_len); + goto err; + } + + /* Piggyback */ + *ctrl_reg = __le16_to_cpu( + ((__le16 *)data)[alloc_len / 2 - 1]); + + wsm = (struct wsm_hdr *)data; + wsm_len = __le16_to_cpu(wsm->len); + if (WARN_ON(wsm_len > read_len)) + goto err; + + if (priv->wsm_enable_wsm_dumps) + print_hex_dump_bytes("<-- ", + DUMP_PREFIX_NONE, + data, wsm_len); + + wsm_id = __le16_to_cpu(wsm->id) & 0xFFF; + wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7; + + skb_trim(skb_rx, wsm_len); + + if (wsm_id == 0x0800) { + wsm_handle_exception(priv, + &data[sizeof(*wsm)], + wsm_len - sizeof(*wsm)); + goto err; + } else if (!rx_resync) { + if (WARN_ON(wsm_seq != priv->wsm_rx_seq)) + goto err; + } + priv->wsm_rx_seq = (wsm_seq + 1) & 7; + rx_resync = 0; + + if (wsm_id & 0x0400) { + int rc = wsm_release_tx_buffer(priv, 1); + if (WARN_ON(rc < 0)) + return rc; + else if (rc > 0) + *tx = 1; + } + + /* cw1200_wsm_rx takes care on SKB livetime */ + if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx))) + goto err; + + if (skb_rx) { + dev_kfree_skb(skb_rx); + skb_rx = NULL; + } + + return 0; + +err: + if (skb_rx) { + dev_kfree_skb(skb_rx); + skb_rx = NULL; + } + return -1; +} + +static int cw1200_bh_tx_helper(struct cw1200_common *priv, + int *pending_tx, + int *tx_burst) +{ + size_t tx_len; + u8 *data; + int ret; + struct wsm_hdr *wsm; + + if (priv->device_can_sleep) { + ret = cw1200_device_wakeup(priv); + if (WARN_ON(ret < 0)) { /* Error in wakeup */ + *pending_tx = 1; + return 0; + } else if (ret) { /* Woke up */ + priv->device_can_sleep = false; + } else { /* Did not awake */ + *pending_tx = 1; + return 0; + } + } + + wsm_alloc_tx_buffer(priv); + ret = wsm_get_tx(priv, &data, &tx_len, tx_burst); + if (ret <= 0) { + wsm_release_tx_buffer(priv, 1); + if (WARN_ON(ret < 0)) + return ret; /* Error */ + return 0; /* No work */ + } + + wsm = (struct wsm_hdr *)data; + BUG_ON(tx_len < sizeof(*wsm)); + BUG_ON(__le16_to_cpu(wsm->len) != tx_len); + + atomic_add(1, &priv->bh_tx); + + tx_len = priv->hwbus_ops->align_size( + priv->hwbus_priv, tx_len); + + /* Check if not exceeding CW1200 capabilities */ + if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE)) + pr_debug("Write aligned len: %zu\n", tx_len); + + wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX)); + wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq)); + + if (WARN_ON(cw1200_data_write(priv, data, tx_len))) { + pr_err("tx blew up, len %zu\n", tx_len); + wsm_release_tx_buffer(priv, 1); + return -1; /* Error */ + } + + if (priv->wsm_enable_wsm_dumps) + print_hex_dump_bytes("--> ", + DUMP_PREFIX_NONE, + data, + __le16_to_cpu(wsm->len)); + + wsm_txed(priv, data); + priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX; + + if (*tx_burst > 1) { + cw1200_debug_tx_burst(priv); + return 1; /* Work remains */ + } + + return 0; +} + +static int cw1200_bh(void *arg) +{ + struct cw1200_common *priv = arg; + int rx, tx, term, suspend; + u16 ctrl_reg = 0; + int tx_allowed; + int pending_tx = 0; + int tx_burst; + long status; + u32 dummy; + int ret; + + for (;;) { + if (!priv->hw_bufs_used && + priv->powersave_enabled && + !priv->device_can_sleep && + !atomic_read(&priv->recent_scan)) { + status = 1 * HZ; + pr_debug("[BH] Device wakedown. No data.\n"); + cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0); + priv->device_can_sleep = true; + } else if (priv->hw_bufs_used) { + /* Interrupt loss detection */ + status = 1 * HZ; + } else { + status = MAX_SCHEDULE_TIMEOUT; + } + + /* Dummy Read for SDIO retry mechanism*/ + if ((priv->hw_type != -1) && + (atomic_read(&priv->bh_rx) == 0) && + (atomic_read(&priv->bh_tx) == 0)) + cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID, + &dummy, sizeof(dummy)); + + pr_debug("[BH] waiting ...\n"); + status = wait_event_interruptible_timeout(priv->bh_wq, ({ + rx = atomic_xchg(&priv->bh_rx, 0); + tx = atomic_xchg(&priv->bh_tx, 0); + term = atomic_xchg(&priv->bh_term, 0); + suspend = pending_tx ? + 0 : atomic_read(&priv->bh_suspend); + (rx || tx || term || suspend || priv->bh_error); + }), status); + + pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n", + rx, tx, term, suspend, priv->bh_error, status); + + /* Did an error occur? */ + if ((status < 0 && status != -ERESTARTSYS) || + term || priv->bh_error) { + break; + } + if (!status) { /* wait_event timed out */ + unsigned long timestamp = jiffies; + long timeout; + int pending = 0; + int i; + + /* Check to see if we have any outstanding frames */ + if (priv->hw_bufs_used && (!rx || !tx)) { + wiphy_warn(priv->hw->wiphy, + "Missed interrupt? (%d frames outstanding)\n", + priv->hw_bufs_used); + rx = 1; + + /* Get a timestamp of "oldest" frame */ + for (i = 0; i < 4; ++i) + pending += cw1200_queue_get_xmit_timestamp( + &priv->tx_queue[i], + ×tamp, + priv->pending_frame_id); + + /* Check if frame transmission is timed out. + * Add an extra second with respect to possible + * interrupt loss. + */ + timeout = timestamp + + WSM_CMD_LAST_CHANCE_TIMEOUT + + 1 * HZ - + jiffies; + + /* And terminate BH thread if the frame is "stuck" */ + if (pending && timeout < 0) { + wiphy_warn(priv->hw->wiphy, + "Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n", + priv->hw_bufs_used, pending, + timestamp, jiffies); + break; + } + } else if (!priv->device_can_sleep && + !atomic_read(&priv->recent_scan)) { + pr_debug("[BH] Device wakedown. Timeout.\n"); + cw1200_reg_write_16(priv, + ST90TDS_CONTROL_REG_ID, 0); + priv->device_can_sleep = true; + } + goto done; + } else if (suspend) { + pr_debug("[BH] Device suspend.\n"); + if (priv->powersave_enabled) { + pr_debug("[BH] Device wakedown. Suspend.\n"); + cw1200_reg_write_16(priv, + ST90TDS_CONTROL_REG_ID, 0); + priv->device_can_sleep = true; + } + + atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED); + wake_up(&priv->bh_evt_wq); + status = wait_event_interruptible(priv->bh_wq, + CW1200_BH_RESUME == atomic_read(&priv->bh_suspend)); + if (status < 0) { + wiphy_err(priv->hw->wiphy, + "Failed to wait for resume: %ld.\n", + status); + break; + } + pr_debug("[BH] Device resume.\n"); + atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED); + wake_up(&priv->bh_evt_wq); + atomic_add(1, &priv->bh_rx); + goto done; + } + + rx: + tx += pending_tx; + pending_tx = 0; + + if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg)) + break; + + /* Don't bother trying to rx unless we have data to read */ + if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) { + ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx); + if (ret < 0) + break; + /* Double up here if there's more data.. */ + if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) { + ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx); + if (ret < 0) + break; + } + } + + tx: + if (tx) { + tx = 0; + + BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers); + tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used; + tx_allowed = tx_burst > 0; + + if (!tx_allowed) { + /* Buffers full. Ensure we process tx + * after we handle rx.. + */ + pending_tx = tx; + goto done_rx; + } + ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst); + if (ret < 0) + break; + if (ret > 0) /* More to transmit */ + tx = ret; + + /* Re-read ctrl reg */ + if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg)) + break; + } + + done_rx: + if (priv->bh_error) + break; + if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) + goto rx; + if (tx) + goto tx; + + done: + /* Re-enable device interrupts */ + priv->hwbus_ops->lock(priv->hwbus_priv); + __cw1200_irq_enable(priv, 1); + priv->hwbus_ops->unlock(priv->hwbus_priv); + } + + /* Explicitly disable device interrupts */ + priv->hwbus_ops->lock(priv->hwbus_priv); + __cw1200_irq_enable(priv, 0); + priv->hwbus_ops->unlock(priv->hwbus_priv); + + if (!term) { + pr_err("[BH] Fatal error, exiting.\n"); + priv->bh_error = 1; + /* TODO: schedule_work(recovery) */ + } + return 0; +} diff --git a/drivers/net/wireless/acc1340/bh.h b/drivers/net/wireless/acc1340/bh.h new file mode 100644 index 0000000000000..af6a4853728f3 --- /dev/null +++ b/drivers/net/wireless/acc1340/bh.h @@ -0,0 +1,28 @@ +/* + * Device handling thread interface for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 CW1200_BH_H +#define CW1200_BH_H + +/* extern */ struct cw1200_common; + +int cw1200_register_bh(struct cw1200_common *priv); +void cw1200_unregister_bh(struct cw1200_common *priv); +void cw1200_irq_handler(struct cw1200_common *priv); +void cw1200_bh_wakeup(struct cw1200_common *priv); +int cw1200_bh_suspend(struct cw1200_common *priv); +int cw1200_bh_resume(struct cw1200_common *priv); +/* Must be called from BH thread. */ +void cw1200_enable_powersave(struct cw1200_common *priv, + bool enable); +int wsm_release_tx_buffer(struct cw1200_common *priv, int count); + +#endif /* CW1200_BH_H */ diff --git a/drivers/net/wireless/acc1340/debug.c b/drivers/net/wireless/acc1340/debug.c new file mode 100644 index 0000000000000..956ff13472c56 --- /dev/null +++ b/drivers/net/wireless/acc1340/debug.c @@ -0,0 +1,430 @@ +/* + * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers + * DebugFS code + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "debug.h" +#include "fwio.h" + +/* join_status */ +static const char * const cw1200_debug_join_status[] = { + "passive", + "monitor", + "station (joining)", + "station (not authenticated yet)", + "station", + "adhoc", + "access point", +}; + +/* WSM_JOIN_PREAMBLE_... */ +static const char * const cw1200_debug_preamble[] = { + "long", + "short", + "long on 1 and 2 Mbps", +}; + + +static const char * const cw1200_debug_link_id[] = { + "OFF", + "REQ", + "SOFT", + "HARD", + "RESET", + "RESET_REMAP", +}; + +static const char *cw1200_debug_mode(int mode) +{ + switch (mode) { + case NL80211_IFTYPE_UNSPECIFIED: + return "unspecified"; + case NL80211_IFTYPE_MONITOR: + return "monitor"; + case NL80211_IFTYPE_STATION: + return "station"; + case NL80211_IFTYPE_ADHOC: + return "adhoc"; + case NL80211_IFTYPE_MESH_POINT: + return "mesh point"; + case NL80211_IFTYPE_AP: + return "access point"; + case NL80211_IFTYPE_P2P_CLIENT: + return "p2p client"; + case NL80211_IFTYPE_P2P_GO: + return "p2p go"; + default: + return "unsupported"; + } +} + +static void cw1200_queue_status_show(struct seq_file *seq, + struct cw1200_queue *q) +{ + int i; + seq_printf(seq, "Queue %d:\n", q->queue_id); + seq_printf(seq, " capacity: %zu\n", q->capacity); + seq_printf(seq, " queued: %zu\n", q->num_queued); + seq_printf(seq, " pending: %zu\n", q->num_pending); + seq_printf(seq, " sent: %zu\n", q->num_sent); + seq_printf(seq, " locked: %s\n", q->tx_locked_cnt ? "yes" : "no"); + seq_printf(seq, " overfull: %s\n", q->overfull ? "yes" : "no"); + seq_puts(seq, " link map: 0-> "); + for (i = 0; i < q->stats->map_capacity; ++i) + seq_printf(seq, "%.2d ", q->link_map_cache[i]); + seq_printf(seq, "<-%zu\n", q->stats->map_capacity); +} + +static void cw1200_debug_print_map(struct seq_file *seq, + struct cw1200_common *priv, + const char *label, + u32 map) +{ + int i; + seq_printf(seq, "%s0-> ", label); + for (i = 0; i < priv->tx_queue_stats.map_capacity; ++i) + seq_printf(seq, "%s ", (map & BIT(i)) ? "**" : ".."); + seq_printf(seq, "<-%zu\n", priv->tx_queue_stats.map_capacity - 1); +} + +static int cw1200_status_show(struct seq_file *seq, void *v) +{ + int i; + struct list_head *item; + struct cw1200_common *priv = seq->private; + struct cw1200_debug_priv *d = priv->debug; + + seq_puts(seq, "CW1200 Wireless LAN driver status\n"); + seq_printf(seq, "Hardware: %d.%d\n", + priv->wsm_caps.hw_id, + priv->wsm_caps.hw_subid); + seq_printf(seq, "Firmware: %s %d.%d\n", + cw1200_fw_types[priv->wsm_caps.fw_type], + priv->wsm_caps.fw_ver, + priv->wsm_caps.fw_build); + seq_printf(seq, "FW API: %d\n", + priv->wsm_caps.fw_api); + seq_printf(seq, "FW caps: 0x%.4X\n", + priv->wsm_caps.fw_cap); + seq_printf(seq, "FW label: '%s'\n", + priv->wsm_caps.fw_label); + seq_printf(seq, "Mode: %s%s\n", + cw1200_debug_mode(priv->mode), + priv->listening ? " (listening)" : ""); + seq_printf(seq, "Join state: %s\n", + cw1200_debug_join_status[priv->join_status]); + if (priv->channel) + seq_printf(seq, "Channel: %d%s\n", + priv->channel->hw_value, + priv->channel_switch_in_progress ? + " (switching)" : ""); + if (priv->rx_filter.promiscuous) + seq_puts(seq, "Filter: promisc\n"); + else if (priv->rx_filter.fcs) + seq_puts(seq, "Filter: fcs\n"); + if (priv->rx_filter.bssid) + seq_puts(seq, "Filter: bssid\n"); + if (!priv->disable_beacon_filter) + seq_puts(seq, "Filter: beacons\n"); + + if (priv->enable_beacon || + priv->mode == NL80211_IFTYPE_AP || + priv->mode == NL80211_IFTYPE_ADHOC || + priv->mode == NL80211_IFTYPE_MESH_POINT || + priv->mode == NL80211_IFTYPE_P2P_GO) + seq_printf(seq, "Beaconing: %s\n", + priv->enable_beacon ? + "enabled" : "disabled"); + + for (i = 0; i < 4; ++i) + seq_printf(seq, "EDCA(%d): %d, %d, %d, %d, %d\n", i, + priv->edca.params[i].cwmin, + priv->edca.params[i].cwmax, + priv->edca.params[i].aifns, + priv->edca.params[i].txop_limit, + priv->edca.params[i].max_rx_lifetime); + + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + static const char *pm_mode = "unknown"; + switch (priv->powersave_mode.mode) { + case WSM_PSM_ACTIVE: + pm_mode = "off"; + break; + case WSM_PSM_PS: + pm_mode = "on"; + break; + case WSM_PSM_FAST_PS: + pm_mode = "dynamic"; + break; + } + seq_printf(seq, "Preamble: %s\n", + cw1200_debug_preamble[priv->association_mode.preamble]); + seq_printf(seq, "AMPDU spcn: %d\n", + priv->association_mode.mpdu_start_spacing); + seq_printf(seq, "Basic rate: 0x%.8X\n", + le32_to_cpu(priv->association_mode.basic_rate_set)); + seq_printf(seq, "Bss lost: %d beacons\n", + priv->bss_params.beacon_lost_count); + seq_printf(seq, "AID: %d\n", + priv->bss_params.aid); + seq_printf(seq, "Rates: 0x%.8X\n", + priv->bss_params.operational_rate_set); + seq_printf(seq, "Powersave: %s\n", pm_mode); + } + seq_printf(seq, "HT: %s\n", + cw1200_is_ht(&priv->ht_info) ? "on" : "off"); + if (cw1200_is_ht(&priv->ht_info)) { + seq_printf(seq, "Greenfield: %s\n", + cw1200_ht_greenfield(&priv->ht_info) ? "yes" : "no"); + seq_printf(seq, "AMPDU dens: %d\n", + cw1200_ht_ampdu_density(&priv->ht_info)); + } + seq_printf(seq, "RSSI thold: %d\n", + priv->cqm_rssi_thold); + seq_printf(seq, "RSSI hyst: %d\n", + priv->cqm_rssi_hyst); + seq_printf(seq, "Long retr: %d\n", + priv->long_frame_max_tx_count); + seq_printf(seq, "Short retr: %d\n", + priv->short_frame_max_tx_count); + spin_lock_bh(&priv->tx_policy_cache.lock); + i = 0; + list_for_each(item, &priv->tx_policy_cache.used) + ++i; + spin_unlock_bh(&priv->tx_policy_cache.lock); + seq_printf(seq, "RC in use: %d\n", i); + + seq_puts(seq, "\n"); + for (i = 0; i < 4; ++i) { + cw1200_queue_status_show(seq, &priv->tx_queue[i]); + seq_puts(seq, "\n"); + } + + cw1200_debug_print_map(seq, priv, "Link map: ", + priv->link_id_map); + cw1200_debug_print_map(seq, priv, "Asleep map: ", + priv->sta_asleep_mask); + cw1200_debug_print_map(seq, priv, "PSPOLL map: ", + priv->pspoll_mask); + + seq_puts(seq, "\n"); + + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (priv->link_id_db[i].status) { + seq_printf(seq, "Link %d: %s, %pM\n", + i + 1, + cw1200_debug_link_id[priv->link_id_db[i].status], + priv->link_id_db[i].mac); + } + } + + seq_puts(seq, "\n"); + + seq_printf(seq, "BH status: %s\n", + atomic_read(&priv->bh_term) ? "terminated" : "alive"); + seq_printf(seq, "Pending RX: %d\n", + atomic_read(&priv->bh_rx)); + seq_printf(seq, "Pending TX: %d\n", + atomic_read(&priv->bh_tx)); + if (priv->bh_error) + seq_printf(seq, "BH errcode: %d\n", + priv->bh_error); + seq_printf(seq, "TX bufs: %d x %d bytes\n", + priv->wsm_caps.input_buffers, + priv->wsm_caps.input_buffer_size); + seq_printf(seq, "Used bufs: %d\n", + priv->hw_bufs_used); + seq_printf(seq, "Powermgmt: %s\n", + priv->powersave_enabled ? "on" : "off"); + seq_printf(seq, "Device: %s\n", + priv->device_can_sleep ? "asleep" : "awake"); + + spin_lock(&priv->wsm_cmd.lock); + seq_printf(seq, "WSM status: %s\n", + priv->wsm_cmd.done ? "idle" : "active"); + seq_printf(seq, "WSM cmd: 0x%.4X (%td bytes)\n", + priv->wsm_cmd.cmd, priv->wsm_cmd.len); + seq_printf(seq, "WSM retval: %d\n", + priv->wsm_cmd.ret); + spin_unlock(&priv->wsm_cmd.lock); + + seq_printf(seq, "Datapath: %s\n", + atomic_read(&priv->tx_lock) ? "locked" : "unlocked"); + if (atomic_read(&priv->tx_lock)) + seq_printf(seq, "TXlock cnt: %d\n", + atomic_read(&priv->tx_lock)); + + seq_printf(seq, "TXed: %d\n", + d->tx); + seq_printf(seq, "AGG TXed: %d\n", + d->tx_agg); + seq_printf(seq, "MULTI TXed: %d (%d)\n", + d->tx_multi, d->tx_multi_frames); + seq_printf(seq, "RXed: %d\n", + d->rx); + seq_printf(seq, "AGG RXed: %d\n", + d->rx_agg); + seq_printf(seq, "TX miss: %d\n", + d->tx_cache_miss); + seq_printf(seq, "TX align: %d\n", + d->tx_align); + seq_printf(seq, "TX burst: %d\n", + d->tx_burst); + seq_printf(seq, "TX TTL: %d\n", + d->tx_ttl); + seq_printf(seq, "Scan: %s\n", + atomic_read(&priv->scan.in_progress) ? "active" : "idle"); + + return 0; +} + +static int cw1200_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, &cw1200_status_show, + inode->i_private); +} + +static const struct file_operations fops_status = { + .open = cw1200_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int cw1200_counters_show(struct seq_file *seq, void *v) +{ + int ret; + struct cw1200_common *priv = seq->private; + struct wsm_mib_counters_table counters; + + ret = wsm_get_counters_table(priv, &counters); + if (ret) + return ret; + +#define PUT_COUNTER(tab, name) \ + seq_printf(seq, "%s:" tab "%d\n", #name, \ + __le32_to_cpu(counters.name)) + + PUT_COUNTER("\t\t", plcp_errors); + PUT_COUNTER("\t\t", fcs_errors); + PUT_COUNTER("\t\t", tx_packets); + PUT_COUNTER("\t\t", rx_packets); + PUT_COUNTER("\t\t", rx_packet_errors); + PUT_COUNTER("\t", rx_decryption_failures); + PUT_COUNTER("\t\t", rx_mic_failures); + PUT_COUNTER("\t", rx_no_key_failures); + PUT_COUNTER("\t", tx_multicast_frames); + PUT_COUNTER("\t", tx_frames_success); + PUT_COUNTER("\t", tx_frame_failures); + PUT_COUNTER("\t", tx_frames_retried); + PUT_COUNTER("\t", tx_frames_multi_retried); + PUT_COUNTER("\t", rx_frame_duplicates); + PUT_COUNTER("\t\t", rts_success); + PUT_COUNTER("\t\t", rts_failures); + PUT_COUNTER("\t\t", ack_failures); + PUT_COUNTER("\t", rx_multicast_frames); + PUT_COUNTER("\t", rx_frames_success); + PUT_COUNTER("\t", rx_cmac_icv_errors); + PUT_COUNTER("\t\t", rx_cmac_replays); + PUT_COUNTER("\t", rx_mgmt_ccmp_replays); + +#undef PUT_COUNTER + + return 0; +} + +static int cw1200_counters_open(struct inode *inode, struct file *file) +{ + return single_open(file, &cw1200_counters_show, + inode->i_private); +} + +static const struct file_operations fops_counters = { + .open = cw1200_counters_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static ssize_t cw1200_wsm_dumps(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct cw1200_common *priv = file->private_data; + char buf[1]; + + if (!count) + return -EINVAL; + if (copy_from_user(buf, user_buf, 1)) + return -EFAULT; + + if (buf[0] == '1') + priv->wsm_enable_wsm_dumps = 1; + else + priv->wsm_enable_wsm_dumps = 0; + + return count; +} + +static const struct file_operations fops_wsm_dumps = { + .open = simple_open, + .write = cw1200_wsm_dumps, + .llseek = default_llseek, +}; + +int cw1200_debug_init(struct cw1200_common *priv) +{ + int ret = -ENOMEM; + struct cw1200_debug_priv *d = kzalloc(sizeof(struct cw1200_debug_priv), + GFP_KERNEL); + priv->debug = d; + if (!d) + return ret; + + d->debugfs_phy = debugfs_create_dir("cw1200", + priv->hw->wiphy->debugfsdir); + if (!d->debugfs_phy) + goto err; + + if (!debugfs_create_file("status", S_IRUSR, d->debugfs_phy, + priv, &fops_status)) + goto err; + + if (!debugfs_create_file("counters", S_IRUSR, d->debugfs_phy, + priv, &fops_counters)) + goto err; + + if (!debugfs_create_file("wsm_dumps", S_IWUSR, d->debugfs_phy, + priv, &fops_wsm_dumps)) + goto err; + + return 0; + +err: + priv->debug = NULL; + debugfs_remove_recursive(d->debugfs_phy); + kfree(d); + return ret; +} + +void cw1200_debug_release(struct cw1200_common *priv) +{ + struct cw1200_debug_priv *d = priv->debug; + if (d) { + debugfs_remove_recursive(d->debugfs_phy); + priv->debug = NULL; + kfree(d); + } +} diff --git a/drivers/net/wireless/acc1340/debug.h b/drivers/net/wireless/acc1340/debug.h new file mode 100644 index 0000000000000..b525aba53bfc0 --- /dev/null +++ b/drivers/net/wireless/acc1340/debug.h @@ -0,0 +1,93 @@ +/* + * DebugFS code for ST-Ericsson CW1200 mac80211 driver + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 CW1200_DEBUG_H_INCLUDED +#define CW1200_DEBUG_H_INCLUDED + +struct cw1200_debug_priv { + struct dentry *debugfs_phy; + int tx; + int tx_agg; + int rx; + int rx_agg; + int tx_multi; + int tx_multi_frames; + int tx_cache_miss; + int tx_align; + int tx_ttl; + int tx_burst; + int ba_cnt; + int ba_acc; + int ba_cnt_rx; + int ba_acc_rx; +}; + +int cw1200_debug_init(struct cw1200_common *priv); +void cw1200_debug_release(struct cw1200_common *priv); + +static inline void cw1200_debug_txed(struct cw1200_common *priv) +{ + ++priv->debug->tx; +} + +static inline void cw1200_debug_txed_agg(struct cw1200_common *priv) +{ + ++priv->debug->tx_agg; +} + +static inline void cw1200_debug_txed_multi(struct cw1200_common *priv, + int count) +{ + ++priv->debug->tx_multi; + priv->debug->tx_multi_frames += count; +} + +static inline void cw1200_debug_rxed(struct cw1200_common *priv) +{ + ++priv->debug->rx; +} + +static inline void cw1200_debug_rxed_agg(struct cw1200_common *priv) +{ + ++priv->debug->rx_agg; +} + +static inline void cw1200_debug_tx_cache_miss(struct cw1200_common *priv) +{ + ++priv->debug->tx_cache_miss; +} + +static inline void cw1200_debug_tx_align(struct cw1200_common *priv) +{ + ++priv->debug->tx_align; +} + +static inline void cw1200_debug_tx_ttl(struct cw1200_common *priv) +{ + ++priv->debug->tx_ttl; +} + +static inline void cw1200_debug_tx_burst(struct cw1200_common *priv) +{ + ++priv->debug->tx_burst; +} + +static inline void cw1200_debug_ba(struct cw1200_common *priv, + int ba_cnt, int ba_acc, + int ba_cnt_rx, int ba_acc_rx) +{ + priv->debug->ba_cnt = ba_cnt; + priv->debug->ba_acc = ba_acc; + priv->debug->ba_cnt_rx = ba_cnt_rx; + priv->debug->ba_acc_rx = ba_acc_rx; +} + +#endif /* CW1200_DEBUG_H_INCLUDED */ diff --git a/drivers/net/wireless/acc1340/fwio.c b/drivers/net/wireless/acc1340/fwio.c new file mode 100644 index 0000000000000..948733a9e06e2 --- /dev/null +++ b/drivers/net/wireless/acc1340/fwio.c @@ -0,0 +1,563 @@ +/* + * Firmware I/O code for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh + * + * 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 "acc1340.h" +#include "fwio.h" +#include "hwio.h" +#include "hwbus.h" +#include "bh.h" + +static int cw1200_get_hw_type(u32 config_reg_val, int *major_revision) +{ + int hw_type = -1; + u32 silicon_type = (config_reg_val >> 24) & 0x7; + u32 silicon_vers = (config_reg_val >> 31) & 0x1; + + switch (silicon_type) { + case 0x00: + *major_revision = 1; + hw_type = HIF_9000_SILICON_VERSATILE; + break; + case 0x01: + case 0x02: /* CW1x00 */ + case 0x04: /* CW1x60 */ + *major_revision = silicon_type; + if (silicon_vers) + hw_type = HIF_8601_VERSATILE; + else + hw_type = HIF_8601_SILICON; + break; + default: + break; + } + + return hw_type; +} + +static int cw1200_load_firmware_cw1200(struct cw1200_common *priv) +{ + int ret, block, num_blocks; + unsigned i; + u32 val32; + u32 put = 0, get = 0; + u8 *buf = NULL; + const char *fw_path; + const struct firmware *firmware = NULL; + int max_check_times = 1; + + /* Macroses are local. */ +#define APB_WRITE(reg, val) \ + do { \ + ret = cw1200_apb_write_32(priv, CW1200_APB(reg), (val)); \ + if (ret < 0) \ + goto exit; \ + } while (0) +#define APB_READ(reg, val) \ + do { \ + ret = cw1200_apb_read_32(priv, CW1200_APB(reg), &(val)); \ + if (ret < 0) \ + goto free_buffer; \ + } while (0) +#define REG_WRITE(reg, val) \ + do { \ + ret = cw1200_reg_write_32(priv, (reg), (val)); \ + if (ret < 0) \ + goto exit; \ + } while (0) +#define REG_READ(reg, val) \ + do { \ + ret = cw1200_reg_read_32(priv, (reg), &(val)); \ + if (ret < 0) \ + goto exit; \ + } while (0) + + switch (priv->hw_revision) { + case CW1200_HW_REV_CUT10: + fw_path = FIRMWARE_CUT10; + if (!priv->sdd_path) + priv->sdd_path = SDD_FILE_10; + break; + case CW1200_HW_REV_CUT11: + fw_path = FIRMWARE_CUT11; + if (!priv->sdd_path) + priv->sdd_path = SDD_FILE_11; + break; + case CW1200_HW_REV_CUT20: + fw_path = FIRMWARE_CUT20; + if (!priv->sdd_path) + priv->sdd_path = SDD_FILE_20; + break; + case CW1200_HW_REV_CUT22: + fw_path = FIRMWARE_CUT22; + if (!priv->sdd_path) + priv->sdd_path = SDD_FILE_22; + break; + case CW1X60_HW_REV: + + fw_path = FIRMWARE_CW1X60; + if (!priv->sdd_path) + priv->sdd_path = SDD_FILE_CW1X60; + break; + default: + pr_err("Invalid silicon revision %d.\n", priv->hw_revision); + return -EINVAL; + } + + /* Initialize common registers */ + APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, DOWNLOAD_ARE_YOU_HERE); + APB_WRITE(DOWNLOAD_PUT_REG, 0); + APB_WRITE(DOWNLOAD_GET_REG, 0); + APB_WRITE(DOWNLOAD_STATUS_REG, DOWNLOAD_PENDING); + APB_WRITE(DOWNLOAD_FLAGS_REG, 0); + + if(priv->hw_revision != CW1X60_HW_REV) { + /* Write the NOP Instruction */ + REG_WRITE(ST90TDS_SRAM_BASE_ADDR_REG_ID, 0xFFF20000); + REG_WRITE(ST90TDS_AHB_DPORT_REG_ID, 0xEAFFFFFE); + } + /* Release CPU from RESET */ + REG_READ(ST90TDS_CONFIG_REG_ID, val32); + val32 &= ~ST90TDS_CONFIG_CPU_RESET_BIT; + REG_WRITE(ST90TDS_CONFIG_REG_ID, val32); + + /* Enable Clock */ + val32 &= ~ST90TDS_CONFIG_CPU_CLK_DIS_BIT; + REG_WRITE(ST90TDS_CONFIG_REG_ID, val32); + + /* Load a firmware file */ + ret = request_firmware(&firmware, fw_path, priv->pdev); + if (ret) { + pr_err("Can't load firmware file %s.\n", fw_path); + goto exit; + } + + buf = kmalloc(DOWNLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA); + if (!buf) { + pr_err("Can't allocate firmware load buffer.\n"); + ret = -ENOMEM; + goto firmware_release; + } + + /* Check if the bootloader is ready */ + for (i = 0; i < 100; i += 1 + i / 2) { + APB_READ(DOWNLOAD_IMAGE_SIZE_REG, val32); + if (val32 == DOWNLOAD_I_AM_HERE) + break; + mdelay(i); + } /* End of for loop */ + + if (val32 != DOWNLOAD_I_AM_HERE) { + pr_err("Bootloader is not ready.\n"); + ret = -ETIMEDOUT; + goto free_buffer; + } + + /* Calculcate number of download blocks */ + num_blocks = (firmware->size - 1) / DOWNLOAD_BLOCK_SIZE + 1; + + /* Updating the length in Download Ctrl Area */ + val32 = firmware->size; /* Explicit cast from size_t to u32 */ + APB_WRITE(DOWNLOAD_IMAGE_SIZE_REG, val32); + + /* Firmware downloading loop */ + for (block = 0; block < num_blocks; block++) { + size_t tx_size; + size_t block_size; + + /* check the download status */ + APB_READ(DOWNLOAD_STATUS_REG, val32); + if (val32 != DOWNLOAD_PENDING) { + pr_err("Bootloader reported error %d.\n", val32); + ret = -EIO; + goto free_buffer; + } + + /* loop until put - get <= 24K */ + for (i = 0; i < 100; i++) { + APB_READ(DOWNLOAD_GET_REG, get); + if ((put - get) <= + (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) + break; + mdelay(i); + } + + if ((put - get) > (DOWNLOAD_FIFO_SIZE - DOWNLOAD_BLOCK_SIZE)) { + pr_err("Timeout waiting for FIFO.\n"); + ret = -ETIMEDOUT; + goto free_buffer; + } + + /* calculate the block size */ + tx_size = block_size = min_t(size_t, firmware->size - put, + DOWNLOAD_BLOCK_SIZE); + + memcpy(buf, &firmware->data[put], block_size); + if (block_size < DOWNLOAD_BLOCK_SIZE) { + memset(&buf[block_size], 0, + DOWNLOAD_BLOCK_SIZE - block_size); + tx_size = DOWNLOAD_BLOCK_SIZE; + } + + /* send the block to sram */ + ret = cw1200_apb_write(priv, + CW1200_APB(DOWNLOAD_FIFO_OFFSET + + (put & (DOWNLOAD_FIFO_SIZE - 1))), + buf, tx_size); + if (ret < 0) { + pr_err("Can't write firmware block @ %d!\n", + put & (DOWNLOAD_FIFO_SIZE - 1)); + goto free_buffer; + } + + /* update the put register */ + put += block_size; + APB_WRITE(DOWNLOAD_PUT_REG, put); + } /* End of firmware download loop */ + + /* Wait for the download completion */ + for (i = 0; i < 300; i += 1 + i / 2) { + APB_READ(DOWNLOAD_STATUS_REG, val32); + if (val32 != DOWNLOAD_PENDING) + break; + mdelay(i); + } + + APB_READ(DOWNLOAD_STATUS_REG, val32); + while(val32 != DOWNLOAD_SUCCESS && max_check_times <=20){ + APB_READ(DOWNLOAD_STATUS_REG, val32); + msleep(500); + max_check_times++; + } + if (val32 != DOWNLOAD_SUCCESS) { + pr_err("Wait for download completion failed: 0x%.8X\n", val32); + ret = -ETIMEDOUT; + goto free_buffer; + } else { + pr_info("Firmware download completed.\n"); + ret = 0; + } + +free_buffer: + kfree(buf); +firmware_release: + release_firmware(firmware); +exit: + return ret; + +#undef APB_WRITE +#undef APB_READ +#undef REG_WRITE +#undef REG_READ +} + + +static int config_reg_read(struct cw1200_common *priv, u32 *val) +{ + switch (priv->hw_type) { + case HIF_9000_SILICON_VERSATILE: { + u16 val16; + int ret = cw1200_reg_read_16(priv, + ST90TDS_CONFIG_REG_ID, + &val16); + if (ret < 0) + return ret; + *val = val16; + return 0; + } + case HIF_8601_VERSATILE: + case HIF_8601_SILICON: + default: + cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, val); + break; + } + return 0; +} + +static int config_reg_write(struct cw1200_common *priv, u32 val) +{ + switch (priv->hw_type) { + case HIF_9000_SILICON_VERSATILE: + return cw1200_reg_write_16(priv, + ST90TDS_CONFIG_REG_ID, + (u16)val); + case HIF_8601_VERSATILE: + case HIF_8601_SILICON: + default: + return cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val); + } + return 0; +} + +static int acc1340_load_firmware_acc1340(struct cw1200_common *hw_priv) +{ + int ret; + const struct firmware *bootloader = NULL; + const char *bl_path = NULL; + u32 *data = NULL; + u32 addr = 0x08000000; + u32 i = 0; + + /* Load a bootloader file */ + bl_path = BOOTLOADER_CW1X60; + ret = request_firmware(&bootloader, bl_path, hw_priv->pdev); + if (ret) { + goto error; + } + + data = (u32 *)bootloader->data; + for(i = 0; i < (bootloader->size)/4; i++) { + cw1200_reg_write_32(hw_priv,ST90TDS_SRAM_BASE_ADDR_REG_ID, addr); + cw1200_reg_write_32(hw_priv,ST90TDS_AHB_DPORT_REG_ID,data[i]); + addr += 4; + } + + cw1200_load_firmware_cw1200(hw_priv); +error: + return 0; +} + +int cw1200_load_firmware(struct cw1200_common *priv) +{ + int ret; + int i; + u32 val32; + u16 val16; + int major_revision = -1; + int max_check_times = 1; + + /* Read CONFIG Register */ + ret = cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + pr_err("Can't read config register.\n"); + goto out; + } + + if (val32 == 0 || val32 == 0xffffffff) { + pr_err("Bad config register value (0x%08x)\n", val32); + ret = -EIO; + goto out; + } + + priv->hw_type = cw1200_get_hw_type(val32, &major_revision); + if (priv->hw_type < 0) { + pr_err("Can't deduce hardware type.\n"); + ret = -ENOTSUPP; + goto out; + } + + /* Set DPLL Reg value, and read back to confirm writes work */ + ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID, + cw1200_dpll_from_clk(priv->hw_refclk)); + if (ret < 0) { + pr_err("Can't write DPLL register.\n"); + goto out; + } + + msleep(20); + + ret = cw1200_reg_read_32(priv, + ST90TDS_TSET_GEN_R_W_REG_ID, &val32); + if (ret < 0) { + pr_err("Can't read DPLL register.\n"); + goto out; + } + + if (val32 != cw1200_dpll_from_clk(priv->hw_refclk)) { + pr_err("Unable to initialise DPLL register. Wrote 0x%.8X, Read 0x%.8X.\n", + cw1200_dpll_from_clk(priv->hw_refclk), val32); + ret = -EIO; + goto out; + } + + /* Set wakeup bit in device */ + ret = cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16); + if (ret < 0) { + pr_err("set_wakeup: can't read control register.\n"); + goto out; + } + + ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, + val16 | ST90TDS_CONT_WUP_BIT); + if (ret < 0) { + pr_err("set_wakeup: can't write control register.\n"); + goto out; + } + + /* Wait for wakeup */ + for (i = 0; i < 300; i += (1 + i / 2)) { + ret = cw1200_reg_read_16(priv, + ST90TDS_CONTROL_REG_ID, &val16); + if (ret < 0) { + pr_err("wait_for_wakeup: can't read control register.\n"); + goto out; + } + + if (val16 & ST90TDS_CONT_RDY_BIT) + break; + + msleep(i); + } + + while((val16 & ST90TDS_CONT_RDY_BIT) == 0 && max_check_times <= 10) { + msleep(500); + cw1200_reg_read_16(priv, ST90TDS_CONTROL_REG_ID, &val16); + max_check_times++; + } + + if ((val16 & ST90TDS_CONT_RDY_BIT) == 0) { + pr_err("wait_for_wakeup: device is not responding.\n"); + ret = -ETIMEDOUT; + goto out; + } + + switch (major_revision) { + case 1: + /* CW1200 Hardware detection logic : Check for CUT1.1 */ + ret = cw1200_ahb_read_32(priv, CW1200_CUT_ID_ADDR, &val32); + if (ret) { + pr_err("HW detection: can't read CUT ID.\n"); + goto out; + } + + switch (val32) { + case CW1200_CUT_11_ID_STR: + pr_info("CW1x00 Cut 1.1 silicon detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT11; + break; + default: + pr_info("CW1x00 Cut 1.0 silicon detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT10; + break; + } + + /* According to ST-E, CUT<2.0 has busted BA TID0-3. + Just disable it entirely... + */ + priv->ba_rx_tid_mask = 0; + priv->ba_tx_tid_mask = 0; + break; + case 2: { + u32 ar1, ar2, ar3; + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR, &ar1); + if (ret) { + pr_err("(1) HW detection: can't read CUT ID\n"); + goto out; + } + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 4, &ar2); + if (ret) { + pr_err("(2) HW detection: can't read CUT ID.\n"); + goto out; + } + + ret = cw1200_ahb_read_32(priv, CW1200_CUT2_ID_ADDR + 8, &ar3); + if (ret) { + pr_err("(3) HW detection: can't read CUT ID.\n"); + goto out; + } + + if (ar1 == CW1200_CUT_22_ID_STR1 && + ar2 == CW1200_CUT_22_ID_STR2 && + ar3 == CW1200_CUT_22_ID_STR3) { + pr_info("CW1x00 Cut 2.2 silicon detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT22; + } else { + pr_info("CW1x00 Cut 2.0 silicon detected.\n"); + priv->hw_revision = CW1200_HW_REV_CUT20; + } + break; + } + case 4: + pr_info("CW1x60 silicon detected.\n"); + priv->hw_revision = CW1X60_HW_REV; + break; + default: + pr_err("Unsupported silicon major revision %d.\n", + major_revision); + ret = -ENOTSUPP; + goto out; + } + + /* Checking for access mode */ + ret = config_reg_read(priv, &val32); + if (ret < 0) { + pr_err("Can't read config register.\n"); + goto out; + } + + if (!(val32 & ST90TDS_CONFIG_ACCESS_MODE_BIT)) { + pr_err("Device is already in QUEUE mode!\n"); + ret = -EINVAL; + goto out; + } + + switch (priv->hw_type) { + case HIF_8601_SILICON: + if (priv->hw_revision == CW1X60_HW_REV) { + ret = acc1340_load_firmware_acc1340(priv); + } + else + ret = cw1200_load_firmware_cw1200(priv); + break; + default: + pr_err("Can't perform firmware load for hw type %d.\n", + priv->hw_type); + ret = -ENOTSUPP; + goto out; + } + if (ret < 0) { + pr_err("Firmware load error.\n"); + goto out; + } + + /* Enable interrupt signalling */ + priv->hwbus_ops->lock(priv->hwbus_priv); + ret = __cw1200_irq_enable(priv, 1); + priv->hwbus_ops->unlock(priv->hwbus_priv); + if (ret < 0) + goto unsubscribe; + + /* Configure device for MESSSAGE MODE */ + ret = config_reg_read(priv, &val32); + if (ret < 0) { + pr_err("Can't read config register.\n"); + goto unsubscribe; + } + ret = config_reg_write(priv, val32 & ~ST90TDS_CONFIG_ACCESS_MODE_BIT); + if (ret < 0) { + pr_err("Can't write config register.\n"); + goto unsubscribe; + } + + /* Unless we read the CONFIG Register we are + * not able to get an interrupt + */ + mdelay(10); + config_reg_read(priv, &val32); + +out: + return ret; + +unsubscribe: + /* Disable interrupt signalling */ + priv->hwbus_ops->lock(priv->hwbus_priv); + ret = __cw1200_irq_enable(priv, 0); + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} diff --git a/drivers/net/wireless/acc1340/fwio.h b/drivers/net/wireless/acc1340/fwio.h new file mode 100644 index 0000000000000..4ad04f846a0f7 --- /dev/null +++ b/drivers/net/wireless/acc1340/fwio.h @@ -0,0 +1,39 @@ +/* + * Firmware API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh + * + * 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 FWIO_H_INCLUDED +#define FWIO_H_INCLUDED + +#define BOOTLOADER_CW1X60 "bootloader_1260.bin" +#define FIRMWARE_CW1X60 "wsm_6010.bin" +#define FIRMWARE_CUT22 "wsm_22.bin" +#define FIRMWARE_CUT20 "wsm_20.bin" +#define FIRMWARE_CUT11 "wsm_11.bin" +#define FIRMWARE_CUT10 "wsm_10.bin" +#define SDD_FILE_CW1X60 "sdd_6010.bin" +#define SDD_FILE_22 "sdd_22.bin" +#define SDD_FILE_20 "sdd_20.bin" +#define SDD_FILE_11 "sdd_11.bin" +#define SDD_FILE_10 "sdd_10.bin" + +int cw1200_load_firmware(struct cw1200_common *priv); + +/* SDD definitions */ +#define SDD_PTA_CFG_ELT_ID 0xEB +#define SDD_REFERENCE_FREQUENCY_ELT_ID 0xc5 +u32 cw1200_dpll_from_clk(u16 clk); + +#endif diff --git a/drivers/net/wireless/acc1340/hwbus.h b/drivers/net/wireless/acc1340/hwbus.h new file mode 100644 index 0000000000000..8b2fc831c3de7 --- /dev/null +++ b/drivers/net/wireless/acc1340/hwbus.h @@ -0,0 +1,33 @@ +/* + * Common hwbus abstraction layer interface for cw1200 wireless driver + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 CW1200_HWBUS_H +#define CW1200_HWBUS_H + +struct hwbus_priv; + +void cw1200_irq_handler(struct cw1200_common *priv); + +/* This MUST be wrapped with hwbus_ops->lock/unlock! */ +int __cw1200_irq_enable(struct cw1200_common *priv, int enable); + +struct hwbus_ops { + int (*hwbus_memcpy_fromio)(struct hwbus_priv *self, unsigned int addr, + void *dst, int count); + int (*hwbus_memcpy_toio)(struct hwbus_priv *self, unsigned int addr, + const void *src, int count); + void (*lock)(struct hwbus_priv *self); + void (*unlock)(struct hwbus_priv *self); + size_t (*align_size)(struct hwbus_priv *self, size_t size); + int (*power_mgmt)(struct hwbus_priv *self, bool suspend); +}; + +#endif /* CW1200_HWBUS_H */ diff --git a/drivers/net/wireless/acc1340/hwio.c b/drivers/net/wireless/acc1340/hwio.c new file mode 100644 index 0000000000000..2e5ac3c4aef41 --- /dev/null +++ b/drivers/net/wireless/acc1340/hwio.c @@ -0,0 +1,312 @@ +/* + * Low-level device IO routines for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * ST-Ericsson UMAC CW1200 driver, which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh + * + * 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 "acc1340.h" +#include "hwio.h" +#include "hwbus.h" + + /* Sdio addr is 4*spi_addr */ +#define SPI_REG_ADDR_TO_SDIO(spi_reg_addr) ((spi_reg_addr) << 2) +#define SDIO_ADDR17BIT(buf_id, mpf, rfu, reg_id_ofs) \ + ((((buf_id) & 0x1F) << 7) \ + | (((mpf) & 1) << 6) \ + | (((rfu) & 1) << 5) \ + | (((reg_id_ofs) & 0x1F) << 0)) +#define MAX_RETRY 3 + + +static int __cw1200_reg_read(struct cw1200_common *priv, u16 addr, + void *buf, size_t buf_len, int buf_id) +{ + u16 addr_sdio; + u32 sdio_reg_addr_17bit; + + /* Check if buffer is aligned to 4 byte boundary */ + if (WARN_ON(((unsigned long)buf & 3) && (buf_len > 4))) { + pr_err("buffer is not aligned.\n"); + return -EINVAL; + } + + /* Convert to SDIO Register Address */ + addr_sdio = SPI_REG_ADDR_TO_SDIO(addr); + sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio); + + return priv->hwbus_ops->hwbus_memcpy_fromio(priv->hwbus_priv, + sdio_reg_addr_17bit, + buf, buf_len); +} + +static int __cw1200_reg_write(struct cw1200_common *priv, u16 addr, + const void *buf, size_t buf_len, int buf_id) +{ + u16 addr_sdio; + u32 sdio_reg_addr_17bit; + + /* Convert to SDIO Register Address */ + addr_sdio = SPI_REG_ADDR_TO_SDIO(addr); + sdio_reg_addr_17bit = SDIO_ADDR17BIT(buf_id, 0, 0, addr_sdio); + + return priv->hwbus_ops->hwbus_memcpy_toio(priv->hwbus_priv, + sdio_reg_addr_17bit, + buf, buf_len); +} + +static inline int __cw1200_reg_read_32(struct cw1200_common *priv, + u16 addr, u32 *val) +{ + __le32 tmp; + int i = __cw1200_reg_read(priv, addr, &tmp, sizeof(tmp), 0); + *val = le32_to_cpu(tmp); + return i; +} + +static inline int __cw1200_reg_write_32(struct cw1200_common *priv, + u16 addr, u32 val) +{ + __le32 tmp = cpu_to_le32(val); + return __cw1200_reg_write(priv, addr, &tmp, sizeof(tmp), 0); +} + +static inline int __cw1200_reg_read_16(struct cw1200_common *priv, + u16 addr, u16 *val) +{ + __le16 tmp; + int i = __cw1200_reg_read(priv, addr, &tmp, sizeof(tmp), 0); + *val = le16_to_cpu(tmp); + return i; +} + +static inline int __cw1200_reg_write_16(struct cw1200_common *priv, + u16 addr, u16 val) +{ + __le16 tmp = cpu_to_le16(val); + return __cw1200_reg_write(priv, addr, &tmp, sizeof(tmp), 0); +} + +int cw1200_reg_read(struct cw1200_common *priv, u16 addr, void *buf, + size_t buf_len) +{ + int ret; + priv->hwbus_ops->lock(priv->hwbus_priv); + ret = __cw1200_reg_read(priv, addr, buf, buf_len, 0); + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int cw1200_reg_write(struct cw1200_common *priv, u16 addr, const void *buf, + size_t buf_len) +{ + int ret; + priv->hwbus_ops->lock(priv->hwbus_priv); + ret = __cw1200_reg_write(priv, addr, buf, buf_len, 0); + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int cw1200_data_read(struct cw1200_common *priv, void *buf, size_t buf_len) +{ + int ret, retry = 1; + int buf_id_rx = priv->buf_id_rx; + + priv->hwbus_ops->lock(priv->hwbus_priv); + + while (retry <= MAX_RETRY) { + ret = __cw1200_reg_read(priv, + ST90TDS_IN_OUT_QUEUE_REG_ID, buf, + buf_len, buf_id_rx + 1); + if (!ret) { + buf_id_rx = (buf_id_rx + 1) & 3; + priv->buf_id_rx = buf_id_rx; + break; + } else { + retry++; + mdelay(1); + pr_err("error :[%d]\n", ret); + } + } + + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int cw1200_data_write(struct cw1200_common *priv, const void *buf, + size_t buf_len) +{ + int ret, retry = 1; + int buf_id_tx = priv->buf_id_tx; + + priv->hwbus_ops->lock(priv->hwbus_priv); + + while (retry <= MAX_RETRY) { + ret = __cw1200_reg_write(priv, + ST90TDS_IN_OUT_QUEUE_REG_ID, buf, + buf_len, buf_id_tx); + if (!ret) { + buf_id_tx = (buf_id_tx + 1) & 31; + priv->buf_id_tx = buf_id_tx; + break; + } else { + retry++; + mdelay(1); + pr_err("error :[%d]\n", ret); + } + } + + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf, + size_t buf_len, u32 prefetch, u16 port_addr) +{ + u32 val32 = 0; + int i, ret; + + if ((buf_len / 2) >= 0x1000) { + pr_err("Can't read more than 0xfff words.\n"); + return -EINVAL; + } + + priv->hwbus_ops->lock(priv->hwbus_priv); + /* Write address */ + ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr); + if (ret < 0) { + pr_err("Can't write address register.\n"); + goto out; + } + + /* Read CONFIG Register Value - We will read 32 bits */ + ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + pr_err("Can't read config register.\n"); + goto out; + } + + /* Set PREFETCH bit */ + ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, + val32 | prefetch); + if (ret < 0) { + pr_err("Can't write prefetch bit.\n"); + goto out; + } + + /* Check for PRE-FETCH bit to be cleared */ + for (i = 0; i < 20; i++) { + ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + pr_err("Can't check prefetch bit.\n"); + goto out; + } + if (!(val32 & prefetch)) + break; + + mdelay(i); + } + + if (val32 & prefetch) { + pr_err("Prefetch bit is not cleared.\n"); + goto out; + } + + /* Read data port */ + ret = __cw1200_reg_read(priv, port_addr, buf, buf_len, 0); + if (ret < 0) { + pr_err("Can't read data port.\n"); + goto out; + } + +out: + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf, + size_t buf_len) +{ + int ret; + + if ((buf_len / 2) >= 0x1000) { + pr_err("Can't write more than 0xfff words.\n"); + return -EINVAL; + } + + priv->hwbus_ops->lock(priv->hwbus_priv); + + /* Write address */ + ret = __cw1200_reg_write_32(priv, ST90TDS_SRAM_BASE_ADDR_REG_ID, addr); + if (ret < 0) { + pr_err("Can't write address register.\n"); + goto out; + } + + /* Write data port */ + ret = __cw1200_reg_write(priv, ST90TDS_SRAM_DPORT_REG_ID, + buf, buf_len, 0); + if (ret < 0) { + pr_err("Can't write data port.\n"); + goto out; + } + +out: + priv->hwbus_ops->unlock(priv->hwbus_priv); + return ret; +} + +int __cw1200_irq_enable(struct cw1200_common *priv, int enable) +{ + u32 val32; + u16 val16; + int ret; + + if (HIF_8601_SILICON == priv->hw_type) { + ret = __cw1200_reg_read_32(priv, ST90TDS_CONFIG_REG_ID, &val32); + if (ret < 0) { + pr_err("Can't read config register.\n"); + return ret; + } + + if (enable) + val32 |= ST90TDS_CONF_IRQ_RDY_ENABLE; + else + val32 &= ~ST90TDS_CONF_IRQ_RDY_ENABLE; + + ret = __cw1200_reg_write_32(priv, ST90TDS_CONFIG_REG_ID, val32); + if (ret < 0) { + pr_err("Can't write config register.\n"); + return ret; + } + } else { + ret = __cw1200_reg_read_16(priv, ST90TDS_CONFIG_REG_ID, &val16); + if (ret < 0) { + pr_err("Can't read control register.\n"); + return ret; + } + + if (enable) + val16 |= ST90TDS_CONT_IRQ_RDY_ENABLE; + else + val16 &= ~ST90TDS_CONT_IRQ_RDY_ENABLE; + + ret = __cw1200_reg_write_16(priv, ST90TDS_CONFIG_REG_ID, val16); + if (ret < 0) { + pr_err("Can't write control register.\n"); + return ret; + } + } + return 0; +} diff --git a/drivers/net/wireless/acc1340/hwio.h b/drivers/net/wireless/acc1340/hwio.h new file mode 100644 index 0000000000000..ddf52669dc5bd --- /dev/null +++ b/drivers/net/wireless/acc1340/hwio.h @@ -0,0 +1,247 @@ +/* + * Low-level API for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * ST-Ericsson UMAC CW1200 driver which is + * Copyright (c) 2010, ST-Ericsson + * Author: Ajitpal Singh + * + * 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 CW1200_HWIO_H_INCLUDED +#define CW1200_HWIO_H_INCLUDED + +/* extern */ struct cw1200_common; + +#define CW1200_CUT_11_ID_STR (0x302E3830) +#define CW1200_CUT_22_ID_STR1 (0x302e3132) +#define CW1200_CUT_22_ID_STR2 (0x32302e30) +#define CW1200_CUT_22_ID_STR3 (0x3335) +#define CW1200_CUT_ID_ADDR (0xFFF17F90) +#define CW1200_CUT2_ID_ADDR (0xFFF1FF90) + +/* Download control area */ +/* boot loader start address in SRAM */ +#define DOWNLOAD_BOOT_LOADER_OFFSET (0x00000000) +/* 32K, 0x4000 to 0xDFFF */ +#define DOWNLOAD_FIFO_OFFSET (0x00004000) +/* 32K */ +#define DOWNLOAD_FIFO_SIZE (0x00008000) +/* 128 bytes, 0xFF80 to 0xFFFF */ +#define DOWNLOAD_CTRL_OFFSET (0x0000FF80) +#define DOWNLOAD_CTRL_DATA_DWORDS (32-6) + +struct download_cntl_t { + /* size of whole firmware file (including Cheksum), host init */ + u32 image_size; + /* downloading flags */ + u32 flags; + /* No. of bytes put into the download, init & updated by host */ + u32 put; + /* last traced program counter, last ARM reg_pc */ + u32 trace_pc; + /* No. of bytes read from the download, host init, device updates */ + u32 get; + /* r0, boot losader status, host init to pending, device updates */ + u32 status; + /* Extra debug info, r1 to r14 if status=r0=DOWNLOAD_EXCEPTION */ + u32 debug_data[DOWNLOAD_CTRL_DATA_DWORDS]; +}; + +#define DOWNLOAD_IMAGE_SIZE_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, image_size)) +#define DOWNLOAD_FLAGS_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, flags)) +#define DOWNLOAD_PUT_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, put)) +#define DOWNLOAD_TRACE_PC_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, trace_pc)) +#define DOWNLOAD_GET_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, get)) +#define DOWNLOAD_STATUS_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, status)) +#define DOWNLOAD_DEBUG_DATA_REG \ + (DOWNLOAD_CTRL_OFFSET + offsetof(struct download_cntl_t, debug_data)) +#define DOWNLOAD_DEBUG_DATA_LEN (108) + +#define DOWNLOAD_BLOCK_SIZE (1024) + +/* For boot loader detection */ +#define DOWNLOAD_ARE_YOU_HERE (0x87654321) +#define DOWNLOAD_I_AM_HERE (0x12345678) + +/* Download error code */ +#define DOWNLOAD_PENDING (0xFFFFFFFF) +#define DOWNLOAD_SUCCESS (0) +#define DOWNLOAD_EXCEPTION (1) +#define DOWNLOAD_ERR_MEM_1 (2) +#define DOWNLOAD_ERR_MEM_2 (3) +#define DOWNLOAD_ERR_SOFTWARE (4) +#define DOWNLOAD_ERR_FILE_SIZE (5) +#define DOWNLOAD_ERR_CHECKSUM (6) +#define DOWNLOAD_ERR_OVERFLOW (7) +#define DOWNLOAD_ERR_IMAGE (8) +#define DOWNLOAD_ERR_HOST (9) +#define DOWNLOAD_ERR_ABORT (10) + + +#define SYS_BASE_ADDR_SILICON (0) +#define PAC_BASE_ADDRESS_SILICON (SYS_BASE_ADDR_SILICON + 0x09000000) +#define PAC_SHARED_MEMORY_SILICON (PAC_BASE_ADDRESS_SILICON) + +#define CW1200_APB(addr) (PAC_SHARED_MEMORY_SILICON + (addr)) + +/* Device register definitions */ + +/* WBF - SPI Register Addresses */ +#define ST90TDS_ADDR_ID_BASE (0x0000) +/* 16/32 bits */ +#define ST90TDS_CONFIG_REG_ID (0x0000) +/* 16/32 bits */ +#define ST90TDS_CONTROL_REG_ID (0x0001) +/* 16 bits, Q mode W/R */ +#define ST90TDS_IN_OUT_QUEUE_REG_ID (0x0002) +/* 32 bits, AHB bus R/W */ +#define ST90TDS_AHB_DPORT_REG_ID (0x0003) +/* 16/32 bits */ +#define ST90TDS_SRAM_BASE_ADDR_REG_ID (0x0004) +/* 32 bits, APB bus R/W */ +#define ST90TDS_SRAM_DPORT_REG_ID (0x0005) +/* 32 bits, t_settle/general */ +#define ST90TDS_TSET_GEN_R_W_REG_ID (0x0006) +/* 16 bits, Q mode read, no length */ +#define ST90TDS_FRAME_OUT_REG_ID (0x0007) +#define ST90TDS_ADDR_ID_MAX (ST90TDS_FRAME_OUT_REG_ID) + +/* WBF - Control register bit set */ +/* next o/p length, bit 11 to 0 */ +#define ST90TDS_CONT_NEXT_LEN_MASK (0x0FFF) +#define ST90TDS_CONT_WUP_BIT (BIT(12)) +#define ST90TDS_CONT_RDY_BIT (BIT(13)) +#define ST90TDS_CONT_IRQ_ENABLE (BIT(14)) +#define ST90TDS_CONT_RDY_ENABLE (BIT(15)) +#define ST90TDS_CONT_IRQ_RDY_ENABLE (BIT(14)|BIT(15)) + +/* SPI Config register bit set */ +#define ST90TDS_CONFIG_FRAME_BIT (BIT(2)) +#define ST90TDS_CONFIG_WORD_MODE_BITS (BIT(3)|BIT(4)) +#define ST90TDS_CONFIG_WORD_MODE_1 (BIT(3)) +#define ST90TDS_CONFIG_WORD_MODE_2 (BIT(4)) +#define ST90TDS_CONFIG_ERROR_0_BIT (BIT(5)) +#define ST90TDS_CONFIG_ERROR_1_BIT (BIT(6)) +#define ST90TDS_CONFIG_ERROR_2_BIT (BIT(7)) +/* TBD: Sure??? */ +#define ST90TDS_CONFIG_CSN_FRAME_BIT (BIT(7)) +#define ST90TDS_CONFIG_ERROR_3_BIT (BIT(8)) +#define ST90TDS_CONFIG_ERROR_4_BIT (BIT(9)) +/* QueueM */ +#define ST90TDS_CONFIG_ACCESS_MODE_BIT (BIT(10)) +/* AHB bus */ +#define ST90TDS_CONFIG_AHB_PRFETCH_BIT (BIT(11)) +#define ST90TDS_CONFIG_CPU_CLK_DIS_BIT (BIT(12)) +/* APB bus */ +#define ST90TDS_CONFIG_PRFETCH_BIT (BIT(13)) +/* cpu reset */ +#define ST90TDS_CONFIG_CPU_RESET_BIT (BIT(14)) +#define ST90TDS_CONFIG_CLEAR_INT_BIT (BIT(15)) + +/* For CW1200 the IRQ Enable and Ready Bits are in CONFIG register */ +#define ST90TDS_CONF_IRQ_ENABLE (BIT(16)) +#define ST90TDS_CONF_RDY_ENABLE (BIT(17)) +#define ST90TDS_CONF_IRQ_RDY_ENABLE (BIT(16)|BIT(17)) + +int cw1200_data_read(struct cw1200_common *priv, + void *buf, size_t buf_len); +int cw1200_data_write(struct cw1200_common *priv, + const void *buf, size_t buf_len); + +int cw1200_reg_read(struct cw1200_common *priv, u16 addr, + void *buf, size_t buf_len); +int cw1200_reg_write(struct cw1200_common *priv, u16 addr, + const void *buf, size_t buf_len); + +static inline int cw1200_reg_read_16(struct cw1200_common *priv, + u16 addr, u16 *val) +{ + __le32 tmp; + int i; + i = cw1200_reg_read(priv, addr, &tmp, sizeof(tmp)); + *val = le32_to_cpu(tmp) & 0xfffff; + return i; +} + +static inline int cw1200_reg_write_16(struct cw1200_common *priv, + u16 addr, u16 val) +{ + __le32 tmp = cpu_to_le32((u32)val); + return cw1200_reg_write(priv, addr, &tmp, sizeof(tmp)); +} + +static inline int cw1200_reg_read_32(struct cw1200_common *priv, + u16 addr, u32 *val) +{ + __le32 tmp; + int i = cw1200_reg_read(priv, addr, &tmp, sizeof(tmp)); + *val = le32_to_cpu(tmp); + return i; +} + +static inline int cw1200_reg_write_32(struct cw1200_common *priv, + u16 addr, u32 val) +{ + __le32 tmp = cpu_to_le32(val); + return cw1200_reg_write(priv, addr, &tmp, sizeof(val)); +} + +int cw1200_indirect_read(struct cw1200_common *priv, u32 addr, void *buf, + size_t buf_len, u32 prefetch, u16 port_addr); +int cw1200_apb_write(struct cw1200_common *priv, u32 addr, const void *buf, + size_t buf_len); + +static inline int cw1200_apb_read(struct cw1200_common *priv, u32 addr, + void *buf, size_t buf_len) +{ + return cw1200_indirect_read(priv, addr, buf, buf_len, + ST90TDS_CONFIG_PRFETCH_BIT, + ST90TDS_SRAM_DPORT_REG_ID); +} + +static inline int cw1200_ahb_read(struct cw1200_common *priv, u32 addr, + void *buf, size_t buf_len) +{ + return cw1200_indirect_read(priv, addr, buf, buf_len, + ST90TDS_CONFIG_AHB_PRFETCH_BIT, + ST90TDS_AHB_DPORT_REG_ID); +} + +static inline int cw1200_apb_read_32(struct cw1200_common *priv, + u32 addr, u32 *val) +{ + __le32 tmp; + int i = cw1200_apb_read(priv, addr, &tmp, sizeof(tmp)); + *val = le32_to_cpu(tmp); + return i; +} + +static inline int cw1200_apb_write_32(struct cw1200_common *priv, + u32 addr, u32 val) +{ + __le32 tmp = cpu_to_le32(val); + return cw1200_apb_write(priv, addr, &tmp, sizeof(val)); +} +static inline int cw1200_ahb_read_32(struct cw1200_common *priv, + u32 addr, u32 *val) +{ + __le32 tmp; + int i = cw1200_ahb_read(priv, addr, &tmp, sizeof(tmp)); + *val = le32_to_cpu(tmp); + return i; +} + +#endif /* CW1200_HWIO_H_INCLUDED */ diff --git a/drivers/net/wireless/acc1340/main.c b/drivers/net/wireless/acc1340/main.c new file mode 100644 index 0000000000000..8fda613f36516 --- /dev/null +++ b/drivers/net/wireless/acc1340/main.c @@ -0,0 +1,601 @@ +/* + * mac80211 glue code for mac80211 ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on: + * Copyright (c) 2006, Michael Wu + * Copyright (c) 2007-2009, Christian Lamparter + * Copyright 2008, Johannes Berg + * + * Based on: + * - the islsm (softmac prism54) driver, which is: + * Copyright 2004-2006 Jean-Baptiste Note , et al. + * - stlc45xx driver + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + * + * 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 "acc1340.h" +#include "txrx.h" +#include "hwbus.h" +#include "fwio.h" +#include "hwio.h" +#include "bh.h" +#include "sta.h" +#include "scan.h" +#include "debug.h" +#include "pm.h" + +MODULE_AUTHOR("Dmitry Tarnyagin "); +MODULE_DESCRIPTION("Softmac ST-Ericsson CW1200 common code"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("cw1200_core"); + +/* Accept MAC address of the form macaddr=0x00,0x80,0xE1,0x30,0x40,0x50 */ +static u8 cw1200_mac_template[ETH_ALEN] = {0x02, 0x80, 0xe1, 0x00, 0x00, 0x00}; +module_param_array_named(macaddr, cw1200_mac_template, byte, NULL, S_IRUGO); +MODULE_PARM_DESC(macaddr, "Override platform_data MAC address"); + +static char *cw1200_sdd_path; +module_param(cw1200_sdd_path, charp, 0644); +MODULE_PARM_DESC(cw1200_sdd_path, "Override platform_data SDD file"); +static int cw1200_refclk; +module_param(cw1200_refclk, int, 0644); +MODULE_PARM_DESC(cw1200_refclk, "Override platform_data reference clock"); + +int cw1200_power_mode = wsm_power_mode_quiescent; +module_param(cw1200_power_mode, int, 0644); +MODULE_PARM_DESC(cw1200_power_mode, "WSM power mode. 0 == active, 1 == doze, 2 == quiescent (default)"); + +#define RATETAB_ENT(_rate, _rateid, _flags) \ + { \ + .bitrate = (_rate), \ + .hw_value = (_rateid), \ + .flags = (_flags), \ + } + +static struct ieee80211_rate cw1200_rates[] = { + RATETAB_ENT(10, 0, 0), + RATETAB_ENT(20, 1, 0), + RATETAB_ENT(55, 2, 0), + RATETAB_ENT(110, 3, 0), + RATETAB_ENT(60, 6, 0), + RATETAB_ENT(90, 7, 0), + RATETAB_ENT(120, 8, 0), + RATETAB_ENT(180, 9, 0), + RATETAB_ENT(240, 10, 0), + RATETAB_ENT(360, 11, 0), + RATETAB_ENT(480, 12, 0), + RATETAB_ENT(540, 13, 0), +}; + +static struct ieee80211_rate cw1200_mcs_rates[] = { + RATETAB_ENT(65, 14, IEEE80211_TX_RC_MCS), + RATETAB_ENT(130, 15, IEEE80211_TX_RC_MCS), + RATETAB_ENT(195, 16, IEEE80211_TX_RC_MCS), + RATETAB_ENT(260, 17, IEEE80211_TX_RC_MCS), + RATETAB_ENT(390, 18, IEEE80211_TX_RC_MCS), + RATETAB_ENT(520, 19, IEEE80211_TX_RC_MCS), + RATETAB_ENT(585, 20, IEEE80211_TX_RC_MCS), + RATETAB_ENT(650, 21, IEEE80211_TX_RC_MCS), +}; + +#define cw1200_a_rates (cw1200_rates + 4) +#define cw1200_a_rates_size (ARRAY_SIZE(cw1200_rates) - 4) +#define cw1200_g_rates (cw1200_rates + 0) +#define cw1200_g_rates_size (ARRAY_SIZE(cw1200_rates)) +#define cw1200_n_rates (cw1200_mcs_rates) +#define cw1200_n_rates_size (ARRAY_SIZE(cw1200_mcs_rates)) + + +#define CHAN2G(_channel, _freq, _flags) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +#define CHAN5G(_channel, _flags) { \ + .band = IEEE80211_BAND_5GHZ, \ + .center_freq = 5000 + (5 * (_channel)), \ + .hw_value = (_channel), \ + .flags = (_flags), \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel cw1200_2ghz_chantable[] = { + CHAN2G(1, 2412, 0), + CHAN2G(2, 2417, 0), + CHAN2G(3, 2422, 0), + CHAN2G(4, 2427, 0), + CHAN2G(5, 2432, 0), + CHAN2G(6, 2437, 0), + CHAN2G(7, 2442, 0), + CHAN2G(8, 2447, 0), + CHAN2G(9, 2452, 0), + CHAN2G(10, 2457, 0), + CHAN2G(11, 2462, 0), + CHAN2G(12, 2467, 0), + CHAN2G(13, 2472, 0), + CHAN2G(14, 2484, 0), +}; + +static struct ieee80211_channel cw1200_5ghz_chantable[] = { + CHAN5G(34, 0), CHAN5G(36, 0), + CHAN5G(38, 0), CHAN5G(40, 0), + CHAN5G(42, 0), CHAN5G(44, 0), + CHAN5G(46, 0), CHAN5G(48, 0), + CHAN5G(52, 0), CHAN5G(56, 0), + CHAN5G(60, 0), CHAN5G(64, 0), + CHAN5G(100, 0), CHAN5G(104, 0), + CHAN5G(108, 0), CHAN5G(112, 0), + CHAN5G(116, 0), CHAN5G(120, 0), + CHAN5G(124, 0), CHAN5G(128, 0), + CHAN5G(132, 0), CHAN5G(136, 0), + CHAN5G(140, 0), CHAN5G(149, 0), + CHAN5G(153, 0), CHAN5G(157, 0), + CHAN5G(161, 0), CHAN5G(165, 0), + CHAN5G(184, 0), CHAN5G(188, 0), + CHAN5G(192, 0), CHAN5G(196, 0), + CHAN5G(200, 0), CHAN5G(204, 0), + CHAN5G(208, 0), CHAN5G(212, 0), + CHAN5G(216, 0), +}; + +static struct ieee80211_supported_band cw1200_band_2ghz = { + .channels = cw1200_2ghz_chantable, + .n_channels = ARRAY_SIZE(cw1200_2ghz_chantable), + .bitrates = cw1200_g_rates, + .n_bitrates = cw1200_g_rates_size, + .ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | + IEEE80211_HT_CAP_MAX_AMSDU, + .ht_supported = 1, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, + .mcs = { + .rx_mask[0] = 0xFF, + .rx_highest = __cpu_to_le16(0x41), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, + }, +}; + +static struct ieee80211_supported_band cw1200_band_5ghz = { + .channels = cw1200_5ghz_chantable, + .n_channels = ARRAY_SIZE(cw1200_5ghz_chantable), + .bitrates = cw1200_a_rates, + .n_bitrates = cw1200_a_rates_size, + .ht_cap = { + .cap = IEEE80211_HT_CAP_GRN_FLD | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT) | + IEEE80211_HT_CAP_MAX_AMSDU, + .ht_supported = 1, + .ampdu_factor = IEEE80211_HT_MAX_AMPDU_8K, + .ampdu_density = IEEE80211_HT_MPDU_DENSITY_NONE, + .mcs = { + .rx_mask[0] = 0xFF, + .rx_highest = __cpu_to_le16(0x41), + .tx_params = IEEE80211_HT_MCS_TX_DEFINED, + }, + }, +}; + +static const unsigned long cw1200_ttl[] = { + 1 * HZ, /* VO */ + 2 * HZ, /* VI */ + 5 * HZ, /* BE */ + 10 * HZ /* BK */ +}; + +static const struct ieee80211_ops cw1200_ops = { + .start = cw1200_start, + .stop = cw1200_stop, + .add_interface = cw1200_add_interface, + .remove_interface = cw1200_remove_interface, + .change_interface = cw1200_change_interface, + .tx = cw1200_tx, + .hw_scan = cw1200_hw_scan, + .set_tim = cw1200_set_tim, + .sta_notify = cw1200_sta_notify, + .sta_add = cw1200_sta_add, + .sta_remove = cw1200_sta_remove, + .set_key = cw1200_set_key, + .set_rts_threshold = cw1200_set_rts_threshold, + .config = cw1200_config, + .bss_info_changed = cw1200_bss_info_changed, + .prepare_multicast = cw1200_prepare_multicast, + .configure_filter = cw1200_configure_filter, + .conf_tx = cw1200_conf_tx, + .get_stats = cw1200_get_stats, + .ampdu_action = cw1200_ampdu_action, + .flush = cw1200_flush, +#ifdef CONFIG_PM + .suspend = cw1200_wow_suspend, + .resume = cw1200_wow_resume, +#endif + /* Intentionally not offloaded: */ + /*.channel_switch = cw1200_channel_switch, */ + /*.remain_on_channel = cw1200_remain_on_channel, */ + /*.cancel_remain_on_channel = cw1200_cancel_remain_on_channel, */ +}; + +static int cw1200_ba_rx_tids = -1; +static int cw1200_ba_tx_tids = -1; +module_param(cw1200_ba_rx_tids, int, 0644); +module_param(cw1200_ba_tx_tids, int, 0644); +MODULE_PARM_DESC(cw1200_ba_rx_tids, "Block ACK RX TIDs"); +MODULE_PARM_DESC(cw1200_ba_tx_tids, "Block ACK TX TIDs"); + +#ifdef CONFIG_PM +static const struct wiphy_wowlan_support cw1200_wowlan_support = { + /* Support only for limited wowlan functionalities */ + .flags = WIPHY_WOWLAN_ANY | WIPHY_WOWLAN_DISCONNECT, +}; +#endif + + +static struct ieee80211_hw *cw1200_init_common(const u8 *macaddr, + const bool have_5ghz) +{ + int i, band; + struct ieee80211_hw *hw; + struct cw1200_common *priv; + + hw = ieee80211_alloc_hw(sizeof(struct cw1200_common), &cw1200_ops); + if (!hw) + return NULL; + + priv = hw->priv; + priv->hw = hw; + priv->hw_type = -1; + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->rates = cw1200_rates; /* TODO: fetch from FW */ + priv->mcs_rates = cw1200_n_rates; + if (cw1200_ba_rx_tids != -1) + priv->ba_rx_tid_mask = cw1200_ba_rx_tids; + else + priv->ba_rx_tid_mask = 0xFF; /* Enable RX BLKACK for all TIDs */ + if (cw1200_ba_tx_tids != -1) + priv->ba_tx_tid_mask = cw1200_ba_tx_tids; + else + priv->ba_tx_tid_mask = 0xff; /* Enable TX BLKACK for all TIDs */ + + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_SUPPORTS_PS | + IEEE80211_HW_SUPPORTS_DYNAMIC_PS | + IEEE80211_HW_REPORTS_TX_ACK_STATUS | + IEEE80211_HW_CONNECTION_MONITOR | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_TX_AMPDU_SETUP_IN_HW | + IEEE80211_HW_NEED_DTIM_BEFORE_ASSOC; + + hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_ADHOC) | + BIT(NL80211_IFTYPE_AP) | + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_P2P_CLIENT) | + BIT(NL80211_IFTYPE_P2P_GO); + +#ifdef CONFIG_PM + hw->wiphy->wowlan = &cw1200_wowlan_support; +#endif + + hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + + hw->queues = 4; + + priv->rts_threshold = -1; + + hw->max_rates = 8; + hw->max_rate_tries = 15; + hw->extra_tx_headroom = WSM_TX_EXTRA_HEADROOM + + 8; /* TKIP IV */ + + hw->sta_data_size = sizeof(struct cw1200_sta_priv); + + hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &cw1200_band_2ghz; + if (have_5ghz) + hw->wiphy->bands[IEEE80211_BAND_5GHZ] = &cw1200_band_5ghz; + + /* Channel params have to be cleared before registering wiphy again */ + for (band = 0; band < IEEE80211_NUM_BANDS; band++) { + struct ieee80211_supported_band *sband = hw->wiphy->bands[band]; + if (!sband) + continue; + for (i = 0; i < sband->n_channels; i++) { + sband->channels[i].flags = 0; + sband->channels[i].max_antenna_gain = 0; + sband->channels[i].max_power = 30; + } + } + + hw->wiphy->max_scan_ssids = 2; + hw->wiphy->max_scan_ie_len = IEEE80211_MAX_DATA_LEN; + + if (macaddr) + SET_IEEE80211_PERM_ADDR(hw, (u8 *)macaddr); + else + SET_IEEE80211_PERM_ADDR(hw, cw1200_mac_template); + + /* Fix up mac address if necessary */ + if (hw->wiphy->perm_addr[3] == 0 && + hw->wiphy->perm_addr[4] == 0 && + hw->wiphy->perm_addr[5] == 0) { + get_random_bytes(&hw->wiphy->perm_addr[3], 3); + } + + mutex_init(&priv->wsm_cmd_mux); + mutex_init(&priv->conf_mutex); + priv->workqueue = create_singlethread_workqueue("cw1200_wq"); + sema_init(&priv->scan.lock, 1); + INIT_WORK(&priv->scan.work, cw1200_scan_work); + INIT_DELAYED_WORK(&priv->scan.probe_work, cw1200_probe_work); + INIT_DELAYED_WORK(&priv->scan.timeout, cw1200_scan_timeout); + INIT_DELAYED_WORK(&priv->clear_recent_scan_work, + cw1200_clear_recent_scan_work); + INIT_DELAYED_WORK(&priv->join_timeout, cw1200_join_timeout); + INIT_WORK(&priv->unjoin_work, cw1200_unjoin_work); + INIT_WORK(&priv->join_complete_work, cw1200_join_complete_work); + INIT_WORK(&priv->wep_key_work, cw1200_wep_key_work); + INIT_WORK(&priv->tx_policy_upload_work, tx_policy_upload_work); + spin_lock_init(&priv->event_queue_lock); + INIT_LIST_HEAD(&priv->event_queue); + INIT_WORK(&priv->event_handler, cw1200_event_handler); + INIT_DELAYED_WORK(&priv->bss_loss_work, cw1200_bss_loss_work); + INIT_WORK(&priv->bss_params_work, cw1200_bss_params_work); + spin_lock_init(&priv->bss_loss_lock); + spin_lock_init(&priv->ps_state_lock); + INIT_WORK(&priv->set_cts_work, cw1200_set_cts_work); + INIT_WORK(&priv->set_tim_work, cw1200_set_tim_work); + INIT_WORK(&priv->multicast_start_work, cw1200_multicast_start_work); + INIT_WORK(&priv->multicast_stop_work, cw1200_multicast_stop_work); + INIT_WORK(&priv->link_id_work, cw1200_link_id_work); + INIT_DELAYED_WORK(&priv->link_id_gc_work, cw1200_link_id_gc_work); + INIT_WORK(&priv->linkid_reset_work, cw1200_link_id_reset); + INIT_WORK(&priv->update_filtering_work, cw1200_update_filtering_work); + INIT_WORK(&priv->set_beacon_wakeup_period_work, + cw1200_set_beacon_wakeup_period_work); + setup_timer(&priv->mcast_timeout, cw1200_mcast_timeout, + (unsigned long)priv); + + if (cw1200_queue_stats_init(&priv->tx_queue_stats, + CW1200_LINK_ID_MAX, + cw1200_skb_dtor, + priv)) { + ieee80211_free_hw(hw); + return NULL; + } + + for (i = 0; i < 4; ++i) { + if (cw1200_queue_init(&priv->tx_queue[i], + &priv->tx_queue_stats, i, 16, + cw1200_ttl[i])) { + for (; i > 0; i--) + cw1200_queue_deinit(&priv->tx_queue[i - 1]); + cw1200_queue_stats_deinit(&priv->tx_queue_stats); + ieee80211_free_hw(hw); + return NULL; + } + } + + init_waitqueue_head(&priv->channel_switch_done); + init_waitqueue_head(&priv->wsm_cmd_wq); + init_waitqueue_head(&priv->wsm_startup_done); + init_waitqueue_head(&priv->ps_mode_switch_done); + wsm_buf_init(&priv->wsm_cmd_buf); + spin_lock_init(&priv->wsm_cmd.lock); + priv->wsm_cmd.done = 1; + tx_policy_init(priv); + + return hw; +} + +static int cw1200_register_common(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int err; + +#ifdef CONFIG_PM + err = cw1200_pm_init(&priv->pm_state, priv); + if (err) { + pr_err("Cannot init PM. (%d).\n", + err); + return err; + } +#endif + + err = ieee80211_register_hw(dev); + if (err) { + pr_err("Cannot register device (%d).\n", + err); +#ifdef CONFIG_PM + cw1200_pm_deinit(&priv->pm_state); +#endif + return err; + } + + cw1200_debug_init(priv); + + pr_info("Registered as '%s'\n", wiphy_name(dev->wiphy)); + return 0; +} + +static void cw1200_free_common(struct ieee80211_hw *dev) +{ + ieee80211_free_hw(dev); +} + +static void cw1200_unregister_common(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int i; + + ieee80211_unregister_hw(dev); + + del_timer_sync(&priv->mcast_timeout); + cw1200_unregister_bh(priv); + + cw1200_debug_release(priv); + + mutex_destroy(&priv->conf_mutex); + + wsm_buf_deinit(&priv->wsm_cmd_buf); + + destroy_workqueue(priv->workqueue); + priv->workqueue = NULL; + + if (priv->sdd) { + release_firmware(priv->sdd); + priv->sdd = NULL; + } + + for (i = 0; i < 4; ++i) + cw1200_queue_deinit(&priv->tx_queue[i]); + + cw1200_queue_stats_deinit(&priv->tx_queue_stats); +#ifdef CONFIG_PM + cw1200_pm_deinit(&priv->pm_state); +#endif +} + +/* Clock is in KHz */ +u32 cw1200_dpll_from_clk(u16 clk_khz) +{ + switch (clk_khz) { + case 0x32C8: /* 13000 KHz */ + return 0x1D89D241; + case 0x3E80: /* 16000 KHz */ + return 0x000001E1; + case 0x41A0: /* 16800 KHz */ + return 0x124931C1; + case 0x4B00: /* 19200 KHz */ + return 0x00000191; + case 0x5DC0: /* 24000 KHz */ + return 0x00000141; + case 0x6590: /* 26000 KHz */ + return 0x0EC4F121; + case 0x8340: /* 33600 KHz */ + return 0x092490E1; + case 0x9600: /* 38400 KHz */ + return 0x100010C1; + case 0x9C40: /* 40000 KHz */ + return 0x000000C1; + case 0xBB80: /* 48000 KHz */ + return 0x000000A1; + case 0xCB20: /* 52000 KHz */ + return 0x07627091; + default: + pr_err("Unknown Refclk freq (0x%04x), using 26000KHz\n", + clk_khz); + return 0x0EC4F121; + } +} + +int cw1200_core_probe(const struct hwbus_ops *hwbus_ops, + struct hwbus_priv *hwbus, + struct device *pdev, + struct cw1200_common **core, + int ref_clk, const u8 *macaddr, + const char *sdd_path, bool have_5ghz) +{ + int err = -EINVAL; + struct ieee80211_hw *dev; + struct cw1200_common *priv; + struct wsm_operational_mode mode = { + .power_mode = cw1200_power_mode, + .disable_more_flag_usage = true, + }; + + dev = cw1200_init_common(macaddr, have_5ghz); + if (!dev) + goto err; + + priv = dev->priv; + priv->hw_refclk = ref_clk; + if (cw1200_refclk) + priv->hw_refclk = cw1200_refclk; + + priv->sdd_path = (char *)sdd_path; + if (cw1200_sdd_path) + priv->sdd_path = cw1200_sdd_path; + + priv->hwbus_ops = hwbus_ops; + priv->hwbus_priv = hwbus; + priv->pdev = pdev; + SET_IEEE80211_DEV(priv->hw, pdev); + + /* Pass struct cw1200_common back up */ + *core = priv; + + err = cw1200_register_bh(priv); + if (err) + goto err1; + + err = cw1200_load_firmware(priv); + if (err) + goto err2; + + if (wait_event_interruptible_timeout(priv->wsm_startup_done, + priv->firmware_ready, + 3*HZ) <= 0) { + /* TODO: Need to find how to reset device + in QUEUE mode properly. + */ + pr_err("Timeout waiting on device startup\n"); + err = -ETIMEDOUT; + goto err2; + } + + /* Set low-power mode. */ + wsm_set_operational_mode(priv, &mode); + + /* Enable multi-TX confirmation */ + wsm_use_multi_tx_conf(priv, true); + + err = cw1200_register_common(dev); + if (err) + goto err2; + + return err; + +err2: + cw1200_unregister_bh(priv); +err1: + cw1200_free_common(dev); +err: + *core = NULL; + return err; +} +EXPORT_SYMBOL_GPL(cw1200_core_probe); + +void cw1200_core_release(struct cw1200_common *self) +{ + /* Disable device interrupts */ + self->hwbus_ops->lock(self->hwbus_priv); + __cw1200_irq_enable(self, 0); + self->hwbus_ops->unlock(self->hwbus_priv); + + /* And then clean up */ + cw1200_unregister_common(self->hw); + cw1200_free_common(self->hw); + return; +} +EXPORT_SYMBOL_GPL(cw1200_core_release); diff --git a/drivers/net/wireless/acc1340/pm.c b/drivers/net/wireless/acc1340/pm.c new file mode 100644 index 0000000000000..bf7e6b844fac6 --- /dev/null +++ b/drivers/net/wireless/acc1340/pm.c @@ -0,0 +1,367 @@ +/* + * Mac80211 power management API for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "pm.h" +#include "sta.h" +#include "bh.h" +#include "hwbus.h" + +#define CW1200_BEACON_SKIPPING_MULTIPLIER 3 + +struct cw1200_udp_port_filter { + struct wsm_udp_port_filter_hdr hdr; + /* Up to 4 filters are allowed. */ + struct wsm_udp_port_filter filters[WSM_MAX_FILTER_ELEMENTS]; +} __packed; + +struct cw1200_ether_type_filter { + struct wsm_ether_type_filter_hdr hdr; + /* Up to 4 filters are allowed. */ + struct wsm_ether_type_filter filters[WSM_MAX_FILTER_ELEMENTS]; +} __packed; + +static struct cw1200_udp_port_filter cw1200_udp_port_filter_on = { + .hdr.num = 2, + .filters = { + [0] = { + .action = WSM_FILTER_ACTION_FILTER_OUT, + .type = WSM_FILTER_PORT_TYPE_DST, + .port = __cpu_to_le16(67), /* DHCP Bootps */ + }, + [1] = { + .action = WSM_FILTER_ACTION_FILTER_OUT, + .type = WSM_FILTER_PORT_TYPE_DST, + .port = __cpu_to_le16(68), /* DHCP Bootpc */ + }, + } +}; + +static struct wsm_udp_port_filter_hdr cw1200_udp_port_filter_off = { + .num = 0, +}; + +#ifndef ETH_P_WAPI +#define ETH_P_WAPI 0x88B4 +#endif + +static struct cw1200_ether_type_filter cw1200_ether_type_filter_on = { + .hdr.num = 4, + .filters = { + [0] = { + .action = WSM_FILTER_ACTION_FILTER_IN, + .type = __cpu_to_le16(ETH_P_IP), + }, + [1] = { + .action = WSM_FILTER_ACTION_FILTER_IN, + .type = __cpu_to_le16(ETH_P_PAE), + }, + [2] = { + .action = WSM_FILTER_ACTION_FILTER_IN, + .type = __cpu_to_le16(ETH_P_WAPI), + }, + [3] = { + .action = WSM_FILTER_ACTION_FILTER_IN, + .type = __cpu_to_le16(ETH_P_ARP), + }, + }, +}; + +static struct wsm_ether_type_filter_hdr cw1200_ether_type_filter_off = { + .num = 0, +}; + +/* private */ +struct cw1200_suspend_state { + unsigned long bss_loss_tmo; + unsigned long join_tmo; + unsigned long direct_probe; + unsigned long link_id_gc; + bool beacon_skipping; + u8 prev_ps_mode; +}; + +static void cw1200_pm_stay_awake_tmo(unsigned long arg) +{ + /* XXX what's the point of this ? */ +} + +int cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv) +{ + spin_lock_init(&pm->lock); + + setup_timer(&pm->stay_awake, cw1200_pm_stay_awake_tmo, + (unsigned long)pm); + + return 0; +} + +void cw1200_pm_deinit(struct cw1200_pm_state *pm) +{ + del_timer_sync(&pm->stay_awake); +} + +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) +{ + long cur_tmo; + spin_lock_bh(&pm->lock); + cur_tmo = pm->stay_awake.expires - jiffies; + if (!timer_pending(&pm->stay_awake) || cur_tmo < (long)tmo) + mod_timer(&pm->stay_awake, jiffies + tmo); + spin_unlock_bh(&pm->lock); +} + +static long cw1200_suspend_work(struct delayed_work *work) +{ + int ret = cancel_delayed_work(work); + long tmo; + if (ret > 0) { + /* Timer is pending */ + tmo = work->timer.expires - jiffies; + if (tmo < 0) + tmo = 0; + } else { + tmo = -1; + } + return tmo; +} + +static int cw1200_resume_work(struct cw1200_common *priv, + struct delayed_work *work, + unsigned long tmo) +{ + if ((long)tmo < 0) + return 1; + + return queue_delayed_work(priv->workqueue, work, tmo); +} + +int cw1200_can_suspend(struct cw1200_common *priv) +{ + if (atomic_read(&priv->bh_rx)) { + wiphy_dbg(priv->hw->wiphy, "Suspend interrupted.\n"); + return 0; + } + return 1; +} +EXPORT_SYMBOL_GPL(cw1200_can_suspend); + +int cw1200_wow_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; + struct cw1200_suspend_state *state; + int ret; + + spin_lock_bh(&pm_state->lock); + ret = timer_pending(&pm_state->stay_awake); + spin_unlock_bh(&pm_state->lock); + if (ret) + return -EAGAIN; + + /* Do not suspend when datapath is not idle */ + if (priv->tx_queue_stats.num_queued) + return -EBUSY; + + /* Make sure there is no configuration requests in progress. */ + if (!mutex_trylock(&priv->conf_mutex)) + return -EBUSY; + + /* Ensure pending operations are done. + * Note also that wow_suspend must return in ~2.5sec, before + * watchdog is triggered. + */ + if (priv->channel_switch_in_progress) + goto revert1; + + /* Do not suspend when join is pending */ + if (priv->join_pending) + goto revert1; + + /* Do not suspend when scanning */ + if (down_trylock(&priv->scan.lock)) + goto revert1; + + /* Lock TX. */ + wsm_lock_tx_async(priv); + + /* Wait to avoid possible race with bh code. + * But do not wait too long... + */ + if (wait_event_timeout(priv->bh_evt_wq, + !priv->hw_bufs_used, HZ / 10) <= 0) + goto revert2; + + /* Set UDP filter */ + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_on.hdr); + + /* Set ethernet frame type filter */ + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_on.hdr); + + /* Allocate state */ + state = kzalloc(sizeof(struct cw1200_suspend_state), GFP_KERNEL); + if (!state) + goto revert3; + + /* Change to legacy PS while going to suspend */ + if (!priv->vif->p2p && + priv->join_status == CW1200_JOIN_STATUS_STA && + priv->powersave_mode.mode != WSM_PSM_PS) { + state->prev_ps_mode = priv->powersave_mode.mode; + priv->powersave_mode.mode = WSM_PSM_PS; + cw1200_set_pm(priv, &priv->powersave_mode); + if (wait_event_interruptible_timeout(priv->ps_mode_switch_done, + !priv->ps_mode_switch_in_progress, 1*HZ) <= 0) { + goto revert4; + } + } + + /* Store delayed work states. */ + state->bss_loss_tmo = + cw1200_suspend_work(&priv->bss_loss_work); + state->join_tmo = + cw1200_suspend_work(&priv->join_timeout); + state->direct_probe = + cw1200_suspend_work(&priv->scan.probe_work); + state->link_id_gc = + cw1200_suspend_work(&priv->link_id_gc_work); + + cancel_delayed_work_sync(&priv->clear_recent_scan_work); + atomic_set(&priv->recent_scan, 0); + + /* Enable beacon skipping */ + if (priv->join_status == CW1200_JOIN_STATUS_STA && + priv->join_dtim_period && + !priv->has_multicast_subscription) { + state->beacon_skipping = true; + wsm_set_beacon_wakeup_period(priv, + priv->join_dtim_period, + CW1200_BEACON_SKIPPING_MULTIPLIER * priv->join_dtim_period); + } + + /* Stop serving thread */ + if (cw1200_bh_suspend(priv)) + goto revert5; + + ret = timer_pending(&priv->mcast_timeout); + if (ret) + goto revert6; + + /* Store suspend state */ + pm_state->suspend_state = state; + + /* Enable IRQ wake */ + ret = priv->hwbus_ops->power_mgmt(priv->hwbus_priv, true); + if (ret) { + wiphy_err(priv->hw->wiphy, + "PM request failed: %d. WoW is disabled.\n", ret); + cw1200_wow_resume(hw); + return -EBUSY; + } + + /* Force resume if event is coming from the device. */ + if (atomic_read(&priv->bh_rx)) { + cw1200_wow_resume(hw); + return -EAGAIN; + } + + return 0; + +revert6: + WARN_ON(cw1200_bh_resume(priv)); +revert5: + cw1200_resume_work(priv, &priv->bss_loss_work, + state->bss_loss_tmo); + cw1200_resume_work(priv, &priv->join_timeout, + state->join_tmo); + cw1200_resume_work(priv, &priv->scan.probe_work, + state->direct_probe); + cw1200_resume_work(priv, &priv->link_id_gc_work, + state->link_id_gc); +revert4: + kfree(state); +revert3: + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off); + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off); +revert2: + wsm_unlock_tx(priv); + up(&priv->scan.lock); +revert1: + mutex_unlock(&priv->conf_mutex); + return -EBUSY; +} + +int cw1200_wow_resume(struct ieee80211_hw *hw) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_pm_state *pm_state = &priv->pm_state; + struct cw1200_suspend_state *state; + + state = pm_state->suspend_state; + pm_state->suspend_state = NULL; + + /* Disable IRQ wake */ + priv->hwbus_ops->power_mgmt(priv->hwbus_priv, false); + + /* Scan.lock must be released before BH is resumed other way + * in case when BSS_LOST command arrived the processing of the + * command will be delayed. + */ + up(&priv->scan.lock); + + /* Resume BH thread */ + WARN_ON(cw1200_bh_resume(priv)); + + /* Restores previous PS mode */ + if (!priv->vif->p2p && priv->join_status == CW1200_JOIN_STATUS_STA) { + priv->powersave_mode.mode = state->prev_ps_mode; + cw1200_set_pm(priv, &priv->powersave_mode); + } + + if (state->beacon_skipping) { + wsm_set_beacon_wakeup_period(priv, priv->beacon_int * + priv->join_dtim_period > + MAX_BEACON_SKIP_TIME_MS ? 1 : + priv->join_dtim_period, 0); + state->beacon_skipping = false; + } + + /* Resume delayed work */ + cw1200_resume_work(priv, &priv->bss_loss_work, + state->bss_loss_tmo); + cw1200_resume_work(priv, &priv->join_timeout, + state->join_tmo); + cw1200_resume_work(priv, &priv->scan.probe_work, + state->direct_probe); + cw1200_resume_work(priv, &priv->link_id_gc_work, + state->link_id_gc); + + /* Remove UDP port filter */ + wsm_set_udp_port_filter(priv, &cw1200_udp_port_filter_off); + + /* Remove ethernet frame type filter */ + wsm_set_ether_type_filter(priv, &cw1200_ether_type_filter_off); + + /* Unlock datapath */ + wsm_unlock_tx(priv); + + /* Unlock configuration mutex */ + mutex_unlock(&priv->conf_mutex); + + /* Free memory */ + kfree(state); + + return 0; +} diff --git a/drivers/net/wireless/acc1340/pm.h b/drivers/net/wireless/acc1340/pm.h new file mode 100644 index 0000000000000..3ed90ff22bb85 --- /dev/null +++ b/drivers/net/wireless/acc1340/pm.h @@ -0,0 +1,43 @@ +/* + * Mac80211 power management interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2011, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 PM_H_INCLUDED +#define PM_H_INCLUDED + +/* ******************************************************************** */ +/* mac80211 API */ + +/* extern */ struct cw1200_common; +/* private */ struct cw1200_suspend_state; + +struct cw1200_pm_state { + struct cw1200_suspend_state *suspend_state; + struct timer_list stay_awake; + struct platform_device *pm_dev; + spinlock_t lock; /* Protect access */ +}; + +#ifdef CONFIG_PM +int cw1200_pm_init(struct cw1200_pm_state *pm, + struct cw1200_common *priv); +void cw1200_pm_deinit(struct cw1200_pm_state *pm); +int cw1200_wow_suspend(struct ieee80211_hw *hw, + struct cfg80211_wowlan *wowlan); +int cw1200_wow_resume(struct ieee80211_hw *hw); +int cw1200_can_suspend(struct cw1200_common *priv); +void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo); +#else +static inline void cw1200_pm_stay_awake(struct cw1200_pm_state *pm, + unsigned long tmo) { +} +#endif +#endif diff --git a/drivers/net/wireless/acc1340/queue.c b/drivers/net/wireless/acc1340/queue.c new file mode 100644 index 0000000000000..c80dd95535886 --- /dev/null +++ b/drivers/net/wireless/acc1340/queue.c @@ -0,0 +1,581 @@ +/* + * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "queue.h" +#include "acc1340.h" +#include "debug.h" + +/* private */ struct cw1200_queue_item +{ + struct list_head head; + struct sk_buff *skb; + u32 packet_id; + unsigned long queue_timestamp; + unsigned long xmit_timestamp; + struct cw1200_txpriv txpriv; + u8 generation; +}; + +static inline void __cw1200_queue_lock(struct cw1200_queue *queue) +{ + struct cw1200_queue_stats *stats = queue->stats; + if (queue->tx_locked_cnt++ == 0) { + pr_debug("[TX] Queue %d is locked.\n", + queue->queue_id); + ieee80211_stop_queue(stats->priv->hw, queue->queue_id); + } +} + +static inline void __cw1200_queue_unlock(struct cw1200_queue *queue) +{ + struct cw1200_queue_stats *stats = queue->stats; + BUG_ON(!queue->tx_locked_cnt); + if (--queue->tx_locked_cnt == 0) { + pr_debug("[TX] Queue %d is unlocked.\n", + queue->queue_id); + ieee80211_wake_queue(stats->priv->hw, queue->queue_id); + } +} + +static inline void cw1200_queue_parse_id(u32 packet_id, u8 *queue_generation, + u8 *queue_id, u8 *item_generation, + u8 *item_id) +{ + *item_id = (packet_id >> 0) & 0xFF; + *item_generation = (packet_id >> 8) & 0xFF; + *queue_id = (packet_id >> 16) & 0xFF; + *queue_generation = (packet_id >> 24) & 0xFF; +} + +static inline u32 cw1200_queue_mk_packet_id(u8 queue_generation, u8 queue_id, + u8 item_generation, u8 item_id) +{ + return ((u32)item_id << 0) | + ((u32)item_generation << 8) | + ((u32)queue_id << 16) | + ((u32)queue_generation << 24); +} + +static void cw1200_queue_post_gc(struct cw1200_queue_stats *stats, + struct list_head *gc_list) +{ + struct cw1200_queue_item *item, *tmp; + + list_for_each_entry_safe(item, tmp, gc_list, head) { + list_del(&item->head); + stats->skb_dtor(stats->priv, item->skb, &item->txpriv); + kfree(item); + } +} + +static void cw1200_queue_register_post_gc(struct list_head *gc_list, + struct cw1200_queue_item *item) +{ + struct cw1200_queue_item *gc_item; + gc_item = kmalloc(sizeof(struct cw1200_queue_item), + GFP_ATOMIC); + BUG_ON(!gc_item); + memcpy(gc_item, item, sizeof(struct cw1200_queue_item)); + list_add_tail(&gc_item->head, gc_list); +} + +static void __cw1200_queue_gc(struct cw1200_queue *queue, + struct list_head *head, + bool unlock) +{ + struct cw1200_queue_stats *stats = queue->stats; + struct cw1200_queue_item *item = NULL, *tmp; + bool wakeup_stats = false; + + list_for_each_entry_safe(item, tmp, &queue->queue, head) { + if (jiffies - item->queue_timestamp < queue->ttl) + break; + --queue->num_queued; + --queue->link_map_cache[item->txpriv.link_id]; + spin_lock_bh(&stats->lock); + --stats->num_queued; + if (!--stats->link_map_cache[item->txpriv.link_id]) + wakeup_stats = true; + spin_unlock_bh(&stats->lock); + cw1200_debug_tx_ttl(stats->priv); + cw1200_queue_register_post_gc(head, item); + item->skb = NULL; + list_move_tail(&item->head, &queue->free_pool); + } + + if (wakeup_stats) + wake_up(&stats->wait_link_id_empty); + + if (queue->overfull) { + if (queue->num_queued <= (queue->capacity >> 1)) { + queue->overfull = false; + if (unlock) + __cw1200_queue_unlock(queue); + } else if (item) { + unsigned long tmo = item->queue_timestamp + queue->ttl; + mod_timer(&queue->gc, tmo); + cw1200_pm_stay_awake(&stats->priv->pm_state, + tmo - jiffies); + } + } +} + +static void cw1200_queue_gc(unsigned long arg) +{ + LIST_HEAD(list); + struct cw1200_queue *queue = + (struct cw1200_queue *)arg; + + spin_lock_bh(&queue->lock); + __cw1200_queue_gc(queue, &list, true); + spin_unlock_bh(&queue->lock); + cw1200_queue_post_gc(queue->stats, &list); +} + +int cw1200_queue_stats_init(struct cw1200_queue_stats *stats, + size_t map_capacity, + cw1200_queue_skb_dtor_t skb_dtor, + struct cw1200_common *priv) +{ + memset(stats, 0, sizeof(*stats)); + stats->map_capacity = map_capacity; + stats->skb_dtor = skb_dtor; + stats->priv = priv; + spin_lock_init(&stats->lock); + init_waitqueue_head(&stats->wait_link_id_empty); + + stats->link_map_cache = kzalloc(sizeof(int) * map_capacity, + GFP_KERNEL); + if (!stats->link_map_cache) + return -ENOMEM; + + return 0; +} + +int cw1200_queue_init(struct cw1200_queue *queue, + struct cw1200_queue_stats *stats, + u8 queue_id, + size_t capacity, + unsigned long ttl) +{ + size_t i; + + memset(queue, 0, sizeof(*queue)); + queue->stats = stats; + queue->capacity = capacity; + queue->queue_id = queue_id; + queue->ttl = ttl; + INIT_LIST_HEAD(&queue->queue); + INIT_LIST_HEAD(&queue->pending); + INIT_LIST_HEAD(&queue->free_pool); + spin_lock_init(&queue->lock); + setup_timer(&queue->gc, cw1200_queue_gc, (unsigned long)queue); + + queue->pool = kzalloc(sizeof(struct cw1200_queue_item) * capacity, + GFP_KERNEL); + if (!queue->pool) + return -ENOMEM; + + queue->link_map_cache = kzalloc(sizeof(int) * stats->map_capacity, + GFP_KERNEL); + if (!queue->link_map_cache) { + kfree(queue->pool); + queue->pool = NULL; + return -ENOMEM; + } + + for (i = 0; i < capacity; ++i) + list_add_tail(&queue->pool[i].head, &queue->free_pool); + + return 0; +} + +int cw1200_queue_clear(struct cw1200_queue *queue) +{ + int i; + LIST_HEAD(gc_list); + struct cw1200_queue_stats *stats = queue->stats; + struct cw1200_queue_item *item, *tmp; + + spin_lock_bh(&queue->lock); + queue->generation++; + list_splice_tail_init(&queue->queue, &queue->pending); + list_for_each_entry_safe(item, tmp, &queue->pending, head) { + WARN_ON(!item->skb); + cw1200_queue_register_post_gc(&gc_list, item); + item->skb = NULL; + list_move_tail(&item->head, &queue->free_pool); + } + queue->num_queued = 0; + queue->num_pending = 0; + + spin_lock_bh(&stats->lock); + for (i = 0; i < stats->map_capacity; ++i) { + stats->num_queued -= queue->link_map_cache[i]; + stats->link_map_cache[i] -= queue->link_map_cache[i]; + queue->link_map_cache[i] = 0; + } + spin_unlock_bh(&stats->lock); + if (queue->overfull) { + queue->overfull = false; + __cw1200_queue_unlock(queue); + } + spin_unlock_bh(&queue->lock); + wake_up(&stats->wait_link_id_empty); + cw1200_queue_post_gc(stats, &gc_list); + return 0; +} + +void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats) +{ + kfree(stats->link_map_cache); + stats->link_map_cache = NULL; +} + +void cw1200_queue_deinit(struct cw1200_queue *queue) +{ + cw1200_queue_clear(queue); + del_timer_sync(&queue->gc); + INIT_LIST_HEAD(&queue->free_pool); + kfree(queue->pool); + kfree(queue->link_map_cache); + queue->pool = NULL; + queue->link_map_cache = NULL; + queue->capacity = 0; +} + +size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue, + u32 link_id_map) +{ + size_t ret; + int i, bit; + size_t map_capacity = queue->stats->map_capacity; + + if (!link_id_map) + return 0; + + spin_lock_bh(&queue->lock); + if (link_id_map == (u32)-1) { + ret = queue->num_queued - queue->num_pending; + } else { + ret = 0; + for (i = 0, bit = 1; i < map_capacity; ++i, bit <<= 1) { + if (link_id_map & bit) + ret += queue->link_map_cache[i]; + } + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_put(struct cw1200_queue *queue, + struct sk_buff *skb, + struct cw1200_txpriv *txpriv) +{ + int ret = 0; + LIST_HEAD(gc_list); + struct cw1200_queue_stats *stats = queue->stats; + + if (txpriv->link_id >= queue->stats->map_capacity) + return -EINVAL; + + spin_lock_bh(&queue->lock); + if (!WARN_ON(list_empty(&queue->free_pool))) { + struct cw1200_queue_item *item = list_first_entry( + &queue->free_pool, struct cw1200_queue_item, head); + BUG_ON(item->skb); + + list_move_tail(&item->head, &queue->queue); + item->skb = skb; + item->txpriv = *txpriv; + item->generation = 0; + item->packet_id = cw1200_queue_mk_packet_id(queue->generation, + queue->queue_id, + item->generation, + item - queue->pool); + item->queue_timestamp = jiffies; + + ++queue->num_queued; + ++queue->link_map_cache[txpriv->link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[txpriv->link_id]; + spin_unlock_bh(&stats->lock); + + /* TX may happen in parallel sometimes. + * Leave extra queue slots so we don't overflow. + */ + if (queue->overfull == false && + queue->num_queued >= + (queue->capacity - (num_present_cpus() - 1))) { + queue->overfull = true; + __cw1200_queue_lock(queue); + mod_timer(&queue->gc, jiffies); + } + } else { + ret = -ENOENT; + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_get(struct cw1200_queue *queue, + u32 link_id_map, + struct wsm_tx **tx, + struct ieee80211_tx_info **tx_info, + const struct cw1200_txpriv **txpriv) +{ + int ret = -ENOENT; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + bool wakeup_stats = false; + + spin_lock_bh(&queue->lock); + list_for_each_entry(item, &queue->queue, head) { + if (link_id_map & BIT(item->txpriv.link_id)) { + ret = 0; + break; + } + } + + if (!WARN_ON(ret)) { + *tx = (struct wsm_tx *)item->skb->data; + *tx_info = IEEE80211_SKB_CB(item->skb); + *txpriv = &item->txpriv; + (*tx)->packet_id = item->packet_id; + list_move_tail(&item->head, &queue->pending); + ++queue->num_pending; + --queue->link_map_cache[item->txpriv.link_id]; + item->xmit_timestamp = jiffies; + + spin_lock_bh(&stats->lock); + --stats->num_queued; + if (!--stats->link_map_cache[item->txpriv.link_id]) + wakeup_stats = true; + spin_unlock_bh(&stats->lock); + } + spin_unlock_bh(&queue->lock); + if (wakeup_stats) + wake_up(&stats->wait_link_id_empty); + return ret; +} + +int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + + cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (queue_generation != queue->generation) { + ret = -ENOENT; + } else if (item_id >= (unsigned) queue->capacity) { + WARN_ON(1); + ret = -EINVAL; + } else if (item->generation != item_generation) { + WARN_ON(1); + ret = -ENOENT; + } else { + --queue->num_pending; + ++queue->link_map_cache[item->txpriv.link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[item->txpriv.link_id]; + spin_unlock_bh(&stats->lock); + + item->generation = ++item_generation; + item->packet_id = cw1200_queue_mk_packet_id(queue_generation, + queue_id, + item_generation, + item_id); + list_move(&item->head, &queue->queue); + } + spin_unlock_bh(&queue->lock); + return ret; +} + +int cw1200_queue_requeue_all(struct cw1200_queue *queue) +{ + struct cw1200_queue_item *item, *tmp; + struct cw1200_queue_stats *stats = queue->stats; + spin_lock_bh(&queue->lock); + + list_for_each_entry_safe_reverse(item, tmp, &queue->pending, head) { + --queue->num_pending; + ++queue->link_map_cache[item->txpriv.link_id]; + + spin_lock_bh(&stats->lock); + ++stats->num_queued; + ++stats->link_map_cache[item->txpriv.link_id]; + spin_unlock_bh(&stats->lock); + + ++item->generation; + item->packet_id = cw1200_queue_mk_packet_id(queue->generation, + queue->queue_id, + item->generation, + item - queue->pool); + list_move(&item->head, &queue->queue); + } + spin_unlock_bh(&queue->lock); + + return 0; +} + +int cw1200_queue_remove(struct cw1200_queue *queue, u32 packet_id) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + struct cw1200_queue_stats *stats = queue->stats; + struct sk_buff *gc_skb = NULL; + struct cw1200_txpriv gc_txpriv; + + cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (queue_generation != queue->generation) { + ret = -ENOENT; + } else if (item_id >= (unsigned) queue->capacity) { + WARN_ON(1); + ret = -EINVAL; + } else if (item->generation != item_generation) { + WARN_ON(1); + ret = -ENOENT; + } else { + gc_txpriv = item->txpriv; + gc_skb = item->skb; + item->skb = NULL; + --queue->num_pending; + --queue->num_queued; + ++queue->num_sent; + ++item->generation; + /* Do not use list_move_tail here, but list_move: + * try to utilize cache row. + */ + list_move(&item->head, &queue->free_pool); + + if (queue->overfull && + (queue->num_queued <= (queue->capacity >> 1))) { + queue->overfull = false; + __cw1200_queue_unlock(queue); + } + } + spin_unlock_bh(&queue->lock); + + if (gc_skb) + stats->skb_dtor(stats->priv, gc_skb, &gc_txpriv); + + return ret; +} + +int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id, + struct sk_buff **skb, + const struct cw1200_txpriv **txpriv) +{ + int ret = 0; + u8 queue_generation, queue_id, item_generation, item_id; + struct cw1200_queue_item *item; + cw1200_queue_parse_id(packet_id, &queue_generation, &queue_id, + &item_generation, &item_id); + + item = &queue->pool[item_id]; + + spin_lock_bh(&queue->lock); + BUG_ON(queue_id != queue->queue_id); + if (queue_generation != queue->generation) { + ret = -ENOENT; + } else if (item_id >= (unsigned) queue->capacity) { + WARN_ON(1); + ret = -EINVAL; + } else if (item->generation != item_generation) { + WARN_ON(1); + ret = -ENOENT; + } else { + *skb = item->skb; + *txpriv = &item->txpriv; + } + spin_unlock_bh(&queue->lock); + return ret; +} + +void cw1200_queue_lock(struct cw1200_queue *queue) +{ + spin_lock_bh(&queue->lock); + __cw1200_queue_lock(queue); + spin_unlock_bh(&queue->lock); +} + +void cw1200_queue_unlock(struct cw1200_queue *queue) +{ + spin_lock_bh(&queue->lock); + __cw1200_queue_unlock(queue); + spin_unlock_bh(&queue->lock); +} + +bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue, + unsigned long *timestamp, + u32 pending_frame_id) +{ + struct cw1200_queue_item *item; + bool ret; + + spin_lock_bh(&queue->lock); + ret = !list_empty(&queue->pending); + if (ret) { + list_for_each_entry(item, &queue->pending, head) { + if (item->packet_id != pending_frame_id) + if (time_before(item->xmit_timestamp, + *timestamp)) + *timestamp = item->xmit_timestamp; + } + } + spin_unlock_bh(&queue->lock); + return ret; +} + +bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats, + u32 link_id_map) +{ + bool empty = true; + + spin_lock_bh(&stats->lock); + if (link_id_map == (u32)-1) { + empty = stats->num_queued == 0; + } else { + int i; + for (i = 0; i < stats->map_capacity; ++i) { + if (link_id_map & BIT(i)) { + if (stats->link_map_cache[i]) { + empty = false; + break; + } + } + } + } + spin_unlock_bh(&stats->lock); + + return empty; +} diff --git a/drivers/net/wireless/acc1340/queue.h b/drivers/net/wireless/acc1340/queue.h new file mode 100644 index 0000000000000..119f9c79c14e7 --- /dev/null +++ b/drivers/net/wireless/acc1340/queue.h @@ -0,0 +1,116 @@ +/* + * O(1) TX queue with built-in allocator for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 CW1200_QUEUE_H_INCLUDED +#define CW1200_QUEUE_H_INCLUDED + +/* private */ struct cw1200_queue_item; + +/* extern */ struct sk_buff; +/* extern */ struct wsm_tx; +/* extern */ struct cw1200_common; +/* extern */ struct ieee80211_tx_queue_stats; +/* extern */ struct cw1200_txpriv; + +/* forward */ struct cw1200_queue_stats; + +typedef void (*cw1200_queue_skb_dtor_t)(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv); + +struct cw1200_queue { + struct cw1200_queue_stats *stats; + size_t capacity; + size_t num_queued; + size_t num_pending; + size_t num_sent; + struct cw1200_queue_item *pool; + struct list_head queue; + struct list_head free_pool; + struct list_head pending; + int tx_locked_cnt; + int *link_map_cache; + bool overfull; + spinlock_t lock; /* Protect queue entry */ + u8 queue_id; + u8 generation; + struct timer_list gc; + unsigned long ttl; +}; + +struct cw1200_queue_stats { + spinlock_t lock; /* Protect stats entry */ + int *link_map_cache; + int num_queued; + size_t map_capacity; + wait_queue_head_t wait_link_id_empty; + cw1200_queue_skb_dtor_t skb_dtor; + struct cw1200_common *priv; +}; + +struct cw1200_txpriv { + u8 link_id; + u8 raw_link_id; + u8 tid; + u8 rate_id; + u8 offset; +}; + +int cw1200_queue_stats_init(struct cw1200_queue_stats *stats, + size_t map_capacity, + cw1200_queue_skb_dtor_t skb_dtor, + struct cw1200_common *priv); +int cw1200_queue_init(struct cw1200_queue *queue, + struct cw1200_queue_stats *stats, + u8 queue_id, + size_t capacity, + unsigned long ttl); +int cw1200_queue_clear(struct cw1200_queue *queue); +void cw1200_queue_stats_deinit(struct cw1200_queue_stats *stats); +void cw1200_queue_deinit(struct cw1200_queue *queue); + +size_t cw1200_queue_get_num_queued(struct cw1200_queue *queue, + u32 link_id_map); +int cw1200_queue_put(struct cw1200_queue *queue, + struct sk_buff *skb, + struct cw1200_txpriv *txpriv); +int cw1200_queue_get(struct cw1200_queue *queue, + u32 link_id_map, + struct wsm_tx **tx, + struct ieee80211_tx_info **tx_info, + const struct cw1200_txpriv **txpriv); +int cw1200_queue_requeue(struct cw1200_queue *queue, u32 packet_id); +int cw1200_queue_requeue_all(struct cw1200_queue *queue); +int cw1200_queue_remove(struct cw1200_queue *queue, + u32 packet_id); +int cw1200_queue_get_skb(struct cw1200_queue *queue, u32 packet_id, + struct sk_buff **skb, + const struct cw1200_txpriv **txpriv); +void cw1200_queue_lock(struct cw1200_queue *queue); +void cw1200_queue_unlock(struct cw1200_queue *queue); +bool cw1200_queue_get_xmit_timestamp(struct cw1200_queue *queue, + unsigned long *timestamp, + u32 pending_frame_id); + +bool cw1200_queue_stats_is_empty(struct cw1200_queue_stats *stats, + u32 link_id_map); + +static inline u8 cw1200_queue_get_queue_id(u32 packet_id) +{ + return (packet_id >> 16) & 0xFF; +} + +static inline u8 cw1200_queue_get_generation(u32 packet_id) +{ + return (packet_id >> 8) & 0xFF; +} + +#endif /* CW1200_QUEUE_H_INCLUDED */ diff --git a/drivers/net/wireless/acc1340/scan.c b/drivers/net/wireless/acc1340/scan.c new file mode 100644 index 0000000000000..5486a918d6b38 --- /dev/null +++ b/drivers/net/wireless/acc1340/scan.c @@ -0,0 +1,463 @@ +/* + * Scan implementation for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "scan.h" +#include "sta.h" +#include "pm.h" + +static void cw1200_scan_restart_delayed(struct cw1200_common *priv); + +static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan) +{ + int ret, i; + int tmo = 2000; + + switch (priv->join_status) { + case CW1200_JOIN_STATUS_PRE_STA: + case CW1200_JOIN_STATUS_JOINING: + return -EBUSY; + default: + break; + } + + wiphy_dbg(priv->hw->wiphy, "[SCAN] hw req, type %d, %d channels, flags: 0x%x.\n", + scan->type, scan->num_channels, scan->flags); + + for (i = 0; i < scan->num_channels; ++i) + tmo += scan->ch[i].max_chan_time + 10; + + cancel_delayed_work_sync(&priv->clear_recent_scan_work); + atomic_set(&priv->scan.in_progress, 1); + atomic_set(&priv->recent_scan, 1); + cw1200_pm_stay_awake(&priv->pm_state, msecs_to_jiffies(tmo)); + queue_delayed_work(priv->workqueue, &priv->scan.timeout, + msecs_to_jiffies(tmo)); + ret = wsm_scan(priv, scan); + if (ret) { + atomic_set(&priv->scan.in_progress, 0); + cancel_delayed_work_sync(&priv->scan.timeout); + cw1200_scan_restart_delayed(priv); + } + return ret; +} + +int cw1200_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req) +{ + struct cw1200_common *priv = hw->priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST, + }; + int i, ret; + + if (!priv->vif) + return -EINVAL; + + /* Scan when P2P_GO corrupt firmware MiniAP mode */ + if (priv->join_status == CW1200_JOIN_STATUS_AP) + return -EOPNOTSUPP; + + if (req->n_ssids == 1 && !req->ssids[0].ssid_len) + req->n_ssids = 0; + + wiphy_dbg(hw->wiphy, "[SCAN] Scan request for %d SSIDs.\n", + req->n_ssids); + + if (req->n_ssids > WSM_SCAN_MAX_NUM_OF_SSIDS) + return -EINVAL; + + frame.skb = ieee80211_probereq_get(hw, priv->vif->addr, NULL, 0, + req->ie_len); + if (!frame.skb) + return -ENOMEM; + + if (req->ie_len) + memcpy(skb_put(frame.skb, req->ie_len), req->ie, req->ie_len); + + /* will be unlocked in cw1200_scan_work() */ + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + + ret = wsm_set_template_frame(priv, &frame); + if (!ret) { + /* Host want to be the probe responder. */ + ret = wsm_set_probe_responder(priv, true); + } + if (ret) { + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); + dev_kfree_skb(frame.skb); + return ret; + } + + wsm_lock_tx(priv); + + BUG_ON(priv->scan.req); + priv->scan.req = req; + priv->scan.n_ssids = 0; + priv->scan.status = 0; + priv->scan.begin = &req->channels[0]; + priv->scan.curr = priv->scan.begin; + priv->scan.end = &req->channels[req->n_channels]; + priv->scan.output_power = priv->output_power; + + for (i = 0; i < req->n_ssids; ++i) { + struct wsm_ssid *dst = &priv->scan.ssids[priv->scan.n_ssids]; + memcpy(&dst->ssid[0], req->ssids[i].ssid, sizeof(dst->ssid)); + dst->length = req->ssids[i].ssid_len; + ++priv->scan.n_ssids; + } + + mutex_unlock(&priv->conf_mutex); + + if (frame.skb) + dev_kfree_skb(frame.skb); + queue_work(priv->workqueue, &priv->scan.work); + return 0; +} + +void cw1200_scan_work(struct work_struct *work) +{ + struct cw1200_common *priv = container_of(work, struct cw1200_common, + scan.work); + struct ieee80211_channel **it; + struct wsm_scan scan = { + .type = WSM_SCAN_TYPE_FOREGROUND, + .flags = WSM_SCAN_FLAG_SPLIT_METHOD, + }; + bool first_run = (priv->scan.begin == priv->scan.curr && + priv->scan.begin != priv->scan.end); + int i; + + if (first_run) { + /* Firmware gets crazy if scan request is sent + * when STA is joined but not yet associated. + * Force unjoin in this case. + */ + if (cancel_delayed_work_sync(&priv->join_timeout) > 0) + cw1200_join_timeout(&priv->join_timeout.work); + } + + mutex_lock(&priv->conf_mutex); + + if (first_run) { + if (priv->join_status == CW1200_JOIN_STATUS_STA && + !(priv->powersave_mode.mode & WSM_PSM_PS)) { + struct wsm_set_pm pm = priv->powersave_mode; + pm.mode = WSM_PSM_PS; + cw1200_set_pm(priv, &pm); + } else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) { + /* FW bug: driver has to restart p2p-dev mode + * after scan + */ + cw1200_disable_listening(priv); + } + } + + if (!priv->scan.req || (priv->scan.curr == priv->scan.end)) { + if (priv->scan.output_power != priv->output_power) + wsm_set_output_power(priv, priv->output_power * 10); + if (priv->join_status == CW1200_JOIN_STATUS_STA && + !(priv->powersave_mode.mode & WSM_PSM_PS)) + cw1200_set_pm(priv, &priv->powersave_mode); + + if (priv->scan.status < 0) + wiphy_warn(priv->hw->wiphy, + "[SCAN] Scan failed (%d).\n", + priv->scan.status); + else if (priv->scan.req) + wiphy_dbg(priv->hw->wiphy, + "[SCAN] Scan completed.\n"); + else + wiphy_dbg(priv->hw->wiphy, + "[SCAN] Scan canceled.\n"); + + priv->scan.req = NULL; + cw1200_scan_restart_delayed(priv); + wsm_unlock_tx(priv); + mutex_unlock(&priv->conf_mutex); + ieee80211_scan_completed(priv->hw, priv->scan.status ? 1 : 0); + up(&priv->scan.lock); + return; + } else { + struct ieee80211_channel *first = *priv->scan.curr; + for (it = priv->scan.curr + 1, i = 1; + it != priv->scan.end && i < WSM_SCAN_MAX_NUM_OF_CHANNELS; + ++it, ++i) { + if ((*it)->band != first->band) + break; + if (((*it)->flags ^ first->flags) & + IEEE80211_CHAN_NO_IR) + break; + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + (*it)->max_power != first->max_power) + break; + } + scan.band = first->band; + + if (priv->scan.req->no_cck) + scan.max_tx_rate = WSM_TRANSMIT_RATE_6; + else + scan.max_tx_rate = WSM_TRANSMIT_RATE_1; + scan.num_probes = + (first->flags & IEEE80211_CHAN_NO_IR) ? 0 : 2; + scan.num_ssids = priv->scan.n_ssids; + scan.ssids = &priv->scan.ssids[0]; + scan.num_channels = it - priv->scan.curr; + /* TODO: Is it optimal? */ + scan.probe_delay = 100; + /* It is not stated in WSM specification, however + * FW team says that driver may not use FG scan + * when joined. + */ + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + scan.type = WSM_SCAN_TYPE_BACKGROUND; + scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND; + } + scan.ch = kzalloc( + sizeof(struct wsm_scan_ch) * (it - priv->scan.curr), + GFP_KERNEL); + if (!scan.ch) { + priv->scan.status = -ENOMEM; + goto fail; + } + for (i = 0; i < scan.num_channels; ++i) { + scan.ch[i].number = priv->scan.curr[i]->hw_value; + if (priv->scan.curr[i]->flags & IEEE80211_CHAN_NO_IR) { + scan.ch[i].min_chan_time = 50; + scan.ch[i].max_chan_time = 100; + } else { + scan.ch[i].min_chan_time = 10; + scan.ch[i].max_chan_time = 25; + } + } + if (!(first->flags & IEEE80211_CHAN_NO_IR) && + priv->scan.output_power != first->max_power) { + priv->scan.output_power = first->max_power; + wsm_set_output_power(priv, + priv->scan.output_power * 10); + } + priv->scan.status = cw1200_scan_start(priv, &scan); + kfree(scan.ch); + if (priv->scan.status) + goto fail; + priv->scan.curr = it; + } + mutex_unlock(&priv->conf_mutex); + return; + +fail: + priv->scan.curr = priv->scan.end; + mutex_unlock(&priv->conf_mutex); + queue_work(priv->workqueue, &priv->scan.work); + return; +} + +static void cw1200_scan_restart_delayed(struct cw1200_common *priv) +{ + /* FW bug: driver has to restart p2p-dev mode after scan. */ + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) { + cw1200_enable_listening(priv); + cw1200_update_filtering(priv); + } + + if (priv->delayed_unjoin) { + priv->delayed_unjoin = false; + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } else if (priv->delayed_link_loss) { + wiphy_dbg(priv->hw->wiphy, "[CQM] Requeue BSS loss.\n"); + priv->delayed_link_loss = 0; + cw1200_cqm_bssloss_sm(priv, 1, 0, 0); + } +} + +static void cw1200_scan_complete(struct cw1200_common *priv) +{ + queue_delayed_work(priv->workqueue, &priv->clear_recent_scan_work, HZ); + if (priv->scan.direct_probe) { + wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe complete.\n"); + cw1200_scan_restart_delayed(priv); + priv->scan.direct_probe = 0; + up(&priv->scan.lock); + wsm_unlock_tx(priv); + } else { + cw1200_scan_work(&priv->scan.work); + } +} + +void cw1200_scan_failed_cb(struct cw1200_common *priv) +{ + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) + /* STA is stopped. */ + return; + + if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) { + priv->scan.status = -EIO; + queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0); + } +} + + +void cw1200_scan_complete_cb(struct cw1200_common *priv, + struct wsm_scan_complete *arg) +{ + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) + /* STA is stopped. */ + return; + + if (cancel_delayed_work_sync(&priv->scan.timeout) > 0) { + priv->scan.status = 1; + queue_delayed_work(priv->workqueue, &priv->scan.timeout, 0); + } +} + +void cw1200_clear_recent_scan_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, + clear_recent_scan_work.work); + atomic_xchg(&priv->recent_scan, 0); +} + +void cw1200_scan_timeout(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, scan.timeout.work); + if (atomic_xchg(&priv->scan.in_progress, 0)) { + if (priv->scan.status > 0) { + priv->scan.status = 0; + } else if (!priv->scan.status) { + wiphy_warn(priv->hw->wiphy, + "Timeout waiting for scan complete notification.\n"); + priv->scan.status = -ETIMEDOUT; + priv->scan.curr = priv->scan.end; + wsm_stop_scan(priv); + } + cw1200_scan_complete(priv); + } +} + +void cw1200_probe_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, scan.probe_work.work); + u8 queue_id = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queue_id]; + const struct cw1200_txpriv *txpriv; + struct wsm_tx *wsm; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST, + }; + struct wsm_ssid ssids[1] = {{ + .length = 0, + } }; + struct wsm_scan_ch ch[1] = {{ + .min_chan_time = 0, + .max_chan_time = 10, + } }; + struct wsm_scan scan = { + .type = WSM_SCAN_TYPE_FOREGROUND, + .num_probes = 1, + .probe_delay = 0, + .num_channels = 1, + .ssids = ssids, + .ch = ch, + }; + u8 *ies; + size_t ies_len; + int ret; + + wiphy_dbg(priv->hw->wiphy, "[SCAN] Direct probe work.\n"); + + mutex_lock(&priv->conf_mutex); + if (down_trylock(&priv->scan.lock)) { + /* Scan is already in progress. Requeue self. */ + schedule(); + queue_delayed_work(priv->workqueue, &priv->scan.probe_work, + msecs_to_jiffies(100)); + mutex_unlock(&priv->conf_mutex); + return; + } + + /* Make sure we still have a pending probe req */ + if (cw1200_queue_get_skb(queue, priv->pending_frame_id, + &frame.skb, &txpriv)) { + up(&priv->scan.lock); + mutex_unlock(&priv->conf_mutex); + wsm_unlock_tx(priv); + return; + } + wsm = (struct wsm_tx *)frame.skb->data; + scan.max_tx_rate = wsm->max_tx_rate; + scan.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G; + if (priv->join_status == CW1200_JOIN_STATUS_STA || + priv->join_status == CW1200_JOIN_STATUS_IBSS) { + scan.type = WSM_SCAN_TYPE_BACKGROUND; + scan.flags = WSM_SCAN_FLAG_FORCE_BACKGROUND; + } + ch[0].number = priv->channel->hw_value; + + skb_pull(frame.skb, txpriv->offset); + + ies = &frame.skb->data[sizeof(struct ieee80211_hdr_3addr)]; + ies_len = frame.skb->len - sizeof(struct ieee80211_hdr_3addr); + + if (ies_len) { + u8 *ssidie = + (u8 *)cfg80211_find_ie(WLAN_EID_SSID, ies, ies_len); + if (ssidie && ssidie[1] && ssidie[1] <= sizeof(ssids[0].ssid)) { + u8 *nextie = &ssidie[2 + ssidie[1]]; + /* Remove SSID from the IE list. It has to be provided + * as a separate argument in cw1200_scan_start call + */ + + /* Store SSID localy */ + ssids[0].length = ssidie[1]; + memcpy(ssids[0].ssid, &ssidie[2], ssids[0].length); + scan.num_ssids = 1; + + /* Remove SSID from IE list */ + ssidie[1] = 0; + memmove(&ssidie[2], nextie, &ies[ies_len] - nextie); + skb_trim(frame.skb, frame.skb->len - ssids[0].length); + } + } + + /* FW bug: driver has to restart p2p-dev mode after scan */ + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) + cw1200_disable_listening(priv); + ret = wsm_set_template_frame(priv, &frame); + priv->scan.direct_probe = 1; + if (!ret) { + wsm_flush_tx(priv); + ret = cw1200_scan_start(priv, &scan); + } + mutex_unlock(&priv->conf_mutex); + + skb_push(frame.skb, txpriv->offset); + if (!ret) + IEEE80211_SKB_CB(frame.skb)->flags |= IEEE80211_TX_STAT_ACK; + BUG_ON(cw1200_queue_remove(queue, priv->pending_frame_id)); + + if (ret) { + priv->scan.direct_probe = 0; + up(&priv->scan.lock); + wsm_unlock_tx(priv); + } + + return; +} diff --git a/drivers/net/wireless/acc1340/scan.h b/drivers/net/wireless/acc1340/scan.h new file mode 100644 index 0000000000000..cc75459e5784d --- /dev/null +++ b/drivers/net/wireless/acc1340/scan.h @@ -0,0 +1,56 @@ +/* + * Scan interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 SCAN_H_INCLUDED +#define SCAN_H_INCLUDED + +#include +#include "wsm.h" + +/* external */ struct sk_buff; +/* external */ struct cfg80211_scan_request; +/* external */ struct ieee80211_channel; +/* external */ struct ieee80211_hw; +/* external */ struct work_struct; + +struct cw1200_scan { + struct semaphore lock; + struct work_struct work; + struct delayed_work timeout; + struct cfg80211_scan_request *req; + struct ieee80211_channel **begin; + struct ieee80211_channel **curr; + struct ieee80211_channel **end; + struct wsm_ssid ssids[WSM_SCAN_MAX_NUM_OF_SSIDS]; + int output_power; + int n_ssids; + int status; + atomic_t in_progress; + /* Direct probe requests workaround */ + struct delayed_work probe_work; + int direct_probe; +}; + +int cw1200_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_scan_request *hw_req); +void cw1200_scan_work(struct work_struct *work); +void cw1200_scan_timeout(struct work_struct *work); +void cw1200_clear_recent_scan_work(struct work_struct *work); +void cw1200_scan_complete_cb(struct cw1200_common *priv, + struct wsm_scan_complete *arg); +void cw1200_scan_failed_cb(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* Raw probe requests TX workaround */ +void cw1200_probe_work(struct work_struct *work); + +#endif diff --git a/drivers/net/wireless/acc1340/sta.c b/drivers/net/wireless/acc1340/sta.c new file mode 100644 index 0000000000000..4a82b2aa15829 --- /dev/null +++ b/drivers/net/wireless/acc1340/sta.c @@ -0,0 +1,2403 @@ +/* + * Mac80211 STA API for ST-Ericsson CW1200 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "sta.h" +#include "fwio.h" +#include "bh.h" +#include "debug.h" + +#ifndef ERP_INFO_BYTE_OFFSET +#define ERP_INFO_BYTE_OFFSET 2 +#endif + +static void cw1200_do_join(struct cw1200_common *priv); +static void cw1200_do_unjoin(struct cw1200_common *priv); + +static int cw1200_upload_beacon(struct cw1200_common *priv); +static int cw1200_upload_pspoll(struct cw1200_common *priv); +static int cw1200_upload_null(struct cw1200_common *priv); +static int cw1200_upload_qosnull(struct cw1200_common *priv); +static int cw1200_start_ap(struct cw1200_common *priv); +static int cw1200_update_beaconing(struct cw1200_common *priv); +static int cw1200_enable_beaconing(struct cw1200_common *priv, + bool enable); +static void __cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + int link_id); +static int __cw1200_flush(struct cw1200_common *priv, bool drop); + +static inline void __cw1200_free_event_queue(struct list_head *list) +{ + struct cw1200_wsm_event *event, *tmp; + list_for_each_entry_safe(event, tmp, list, link) { + list_del(&event->link); + kfree(event); + } +} + +/* ******************************************************************** */ +/* STA API */ + +int cw1200_start(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + int ret = 0; + + cw1200_pm_stay_awake(&priv->pm_state, HZ); + + mutex_lock(&priv->conf_mutex); + + /* default EDCA */ + WSM_EDCA_SET(&priv->edca, 0, 0x0002, 0x0003, 0x0007, 47, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 1, 0x0002, 0x0007, 0x000f, 94, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 2, 0x0003, 0x000f, 0x03ff, 0, 0xc8, false); + WSM_EDCA_SET(&priv->edca, 3, 0x0007, 0x000f, 0x03ff, 0, 0xc8, false); + ret = wsm_set_edca_params(priv, &priv->edca); + if (ret) + goto out; + + ret = cw1200_set_uapsd_param(priv, &priv->edca); + if (ret) + goto out; + + priv->setbssparams_done = false; + + memcpy(priv->mac_addr, dev->wiphy->perm_addr, ETH_ALEN); + priv->mode = NL80211_IFTYPE_MONITOR; + priv->wep_default_key_id = -1; + + priv->cqm_beacon_loss_count = 10; + + ret = cw1200_setup_mac(priv); + if (ret) + goto out; + +out: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_stop(struct ieee80211_hw *dev) +{ + struct cw1200_common *priv = dev->priv; + LIST_HEAD(list); + int i; + + wsm_lock_tx(priv); + + while (down_trylock(&priv->scan.lock)) { + /* Scan is in progress. Force it to stop. */ + priv->scan.req = NULL; + schedule(); + } + up(&priv->scan.lock); + + cancel_delayed_work_sync(&priv->scan.probe_work); + cancel_delayed_work_sync(&priv->scan.timeout); + cancel_delayed_work_sync(&priv->clear_recent_scan_work); + cancel_delayed_work_sync(&priv->join_timeout); + cw1200_cqm_bssloss_sm(priv, 0, 0, 0); + cancel_work_sync(&priv->unjoin_work); + cancel_delayed_work_sync(&priv->link_id_gc_work); + flush_workqueue(priv->workqueue); + del_timer_sync(&priv->mcast_timeout); + mutex_lock(&priv->conf_mutex); + priv->mode = NL80211_IFTYPE_UNSPECIFIED; + priv->listening = false; + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + __cw1200_free_event_queue(&list); + + + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + priv->join_pending = false; + + for (i = 0; i < 4; i++) + cw1200_queue_clear(&priv->tx_queue[i]); + mutex_unlock(&priv->conf_mutex); + tx_policy_clean(priv); + + /* HACK! */ + if (atomic_xchg(&priv->tx_lock, 1) != 1) + pr_debug("[STA] TX is force-unlocked due to stop request.\n"); + + wsm_unlock_tx(priv); + atomic_xchg(&priv->tx_lock, 0); /* for recovery to work */ +} + +static int cw1200_bssloss_mitigation = 1; +module_param(cw1200_bssloss_mitigation, int, 0644); +MODULE_PARM_DESC(cw1200_bssloss_mitigation, "BSS Loss mitigation. 0 == disabled, 1 == enabled (default)"); + + +void __cw1200_cqm_bssloss_sm(struct cw1200_common *priv, + int init, int good, int bad) +{ + int tx = 0; + + priv->delayed_link_loss = 0; + cancel_work_sync(&priv->bss_params_work); + + pr_debug("[STA] CQM BSSLOSS_SM: state: %d init %d good %d bad: %d txlock: %d uj: %d\n", + priv->bss_loss_state, + init, good, bad, + atomic_read(&priv->tx_lock), + priv->delayed_unjoin); + + /* If we have a pending unjoin */ + if (priv->delayed_unjoin) + return; + + if (init) { + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, + HZ); + priv->bss_loss_state = 0; + + /* Skip the confimration procedure in P2P case */ + if (!priv->vif->p2p && !atomic_read(&priv->tx_lock)) + tx = 1; + } else if (good) { + cancel_delayed_work_sync(&priv->bss_loss_work); + priv->bss_loss_state = 0; + queue_work(priv->workqueue, &priv->bss_params_work); + } else if (bad) { + /* XXX Should we just keep going until we time out? */ + if (priv->bss_loss_state < 3) + tx = 1; + } else { + cancel_delayed_work_sync(&priv->bss_loss_work); + priv->bss_loss_state = 0; + } + + /* Bypass mitigation if it's disabled */ + if (!cw1200_bssloss_mitigation) + tx = 0; + + /* Spit out a NULL packet to our AP if necessary */ + if (tx) { + struct sk_buff *skb; + + priv->bss_loss_state++; + + skb = ieee80211_nullfunc_get(priv->hw, priv->vif); + WARN_ON(!skb); + if (skb) + cw1200_tx(priv->hw, NULL, skb); + } +} + +int cw1200_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + int ret; + struct cw1200_common *priv = dev->priv; + /* __le32 auto_calibration_mode = __cpu_to_le32(1); */ + + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER | + IEEE80211_VIF_SUPPORTS_UAPSD | + IEEE80211_VIF_SUPPORTS_CQM_RSSI; + + mutex_lock(&priv->conf_mutex); + + if (priv->mode != NL80211_IFTYPE_MONITOR) { + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + switch (vif->type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_MESH_POINT: + case NL80211_IFTYPE_AP: + priv->mode = vif->type; + break; + default: + mutex_unlock(&priv->conf_mutex); + return -EOPNOTSUPP; + } + + priv->vif = vif; + memcpy(priv->mac_addr, vif->addr, ETH_ALEN); + ret = cw1200_setup_mac(priv); + /* Enable auto-calibration */ + /* Exception in subsequent channel switch; disabled. + * wsm_write_mib(priv, WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE, + * &auto_calibration_mode, sizeof(auto_calibration_mode)); + */ + + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif) +{ + struct cw1200_common *priv = dev->priv; + struct wsm_reset reset = { + .reset_statistics = true, + }; + int i; + + mutex_lock(&priv->conf_mutex); + switch (priv->join_status) { + case CW1200_JOIN_STATUS_JOINING: + case CW1200_JOIN_STATUS_PRE_STA: + case CW1200_JOIN_STATUS_STA: + case CW1200_JOIN_STATUS_IBSS: + wsm_lock_tx(priv); + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + break; + case CW1200_JOIN_STATUS_AP: + for (i = 0; priv->link_id_map; ++i) { + if (priv->link_id_map & BIT(i)) { + reset.link_id = i; + wsm_reset(priv, &reset); + priv->link_id_map &= ~BIT(i); + } + } + memset(priv->link_id_db, 0, sizeof(priv->link_id_db)); + priv->sta_asleep_mask = 0; + priv->enable_beacon = false; + priv->tx_multicast = false; + priv->aid0_bit_set = false; + priv->buffered_multicasts = false; + priv->pspoll_mask = 0; + reset.link_id = 0; + wsm_reset(priv, &reset); + break; + case CW1200_JOIN_STATUS_MONITOR: + cw1200_update_listening(priv, false); + break; + default: + break; + } + priv->vif = NULL; + priv->mode = NL80211_IFTYPE_MONITOR; + eth_zero_addr(priv->mac_addr); + memset(&priv->p2p_ps_modeinfo, 0, sizeof(priv->p2p_ps_modeinfo)); + cw1200_free_keys(priv); + cw1200_setup_mac(priv); + priv->listening = false; + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + if (!__cw1200_flush(priv, true)) + wsm_unlock_tx(priv); + + mutex_unlock(&priv->conf_mutex); +} + +int cw1200_change_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, + bool p2p) +{ + int ret = 0; + pr_debug("change_interface new: %d (%d), old: %d (%d)\n", new_type, + p2p, vif->type, vif->p2p); + + if (new_type != vif->type || vif->p2p != p2p) { + cw1200_remove_interface(dev, vif); + vif->type = new_type; + vif->p2p = p2p; + ret = cw1200_add_interface(dev, vif); + } + + return ret; +} + +int cw1200_config(struct ieee80211_hw *dev, u32 changed) +{ + int ret = 0; + struct cw1200_common *priv = dev->priv; + struct ieee80211_conf *conf = &dev->conf; + + pr_debug("CONFIG CHANGED: %08x\n", changed); + + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + /* TODO: IEEE80211_CONF_CHANGE_QOS */ + /* TODO: IEEE80211_CONF_CHANGE_LISTEN_INTERVAL */ + + if (changed & IEEE80211_CONF_CHANGE_POWER) { + priv->output_power = conf->power_level; + pr_debug("[STA] TX power: %d\n", priv->output_power); + wsm_set_output_power(priv, priv->output_power * 10); + } + + if ((changed & IEEE80211_CONF_CHANGE_CHANNEL) && + (priv->channel != conf->chandef.chan)) { + struct ieee80211_channel *ch = conf->chandef.chan; + struct wsm_switch_channel channel = { + .channel_number = ch->hw_value, + }; + pr_debug("[STA] Freq %d (wsm ch: %d).\n", + ch->center_freq, ch->hw_value); + + /* __cw1200_flush() implicitly locks tx, if successful */ + if (!__cw1200_flush(priv, false)) { + if (!wsm_switch_channel(priv, &channel)) { + ret = wait_event_timeout(priv->channel_switch_done, + !priv->channel_switch_in_progress, + 3 * HZ); + if (ret) { + /* Already unlocks if successful */ + priv->channel = ch; + ret = 0; + } else { + ret = -ETIMEDOUT; + } + } else { + /* Unlock if switch channel fails */ + wsm_unlock_tx(priv); + } + } + } + + if (changed & IEEE80211_CONF_CHANGE_PS) { + if (!(conf->flags & IEEE80211_CONF_PS)) + priv->powersave_mode.mode = WSM_PSM_ACTIVE; + else if (conf->dynamic_ps_timeout <= 0) + priv->powersave_mode.mode = WSM_PSM_PS; + else + priv->powersave_mode.mode = WSM_PSM_FAST_PS; + + /* Firmware requires that value for this 1-byte field must + * be specified in units of 500us. Values above the 128ms + * threshold are not supported. + */ + if (conf->dynamic_ps_timeout >= 0x80) + priv->powersave_mode.fast_psm_idle_period = 0xFF; + else + priv->powersave_mode.fast_psm_idle_period = + conf->dynamic_ps_timeout << 1; + + if (priv->join_status == CW1200_JOIN_STATUS_STA && + priv->bss_params.aid) + cw1200_set_pm(priv, &priv->powersave_mode); + } + + if (changed & IEEE80211_CONF_CHANGE_MONITOR) { + /* TBD: It looks like it's transparent + * there's a monitor interface present -- use this + * to determine for example whether to calculate + * timestamps for packets or not, do not use instead + * of filter flags! + */ + } + + if (changed & IEEE80211_CONF_CHANGE_IDLE) { + struct wsm_operational_mode mode = { + .power_mode = cw1200_power_mode, + .disable_more_flag_usage = true, + }; + + wsm_lock_tx(priv); + /* Disable p2p-dev mode forced by TX request */ + if ((priv->join_status == CW1200_JOIN_STATUS_MONITOR) && + (conf->flags & IEEE80211_CONF_IDLE) && + !priv->listening) { + cw1200_disable_listening(priv); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + } + wsm_set_operational_mode(priv, &mode); + wsm_unlock_tx(priv); + } + + if (changed & IEEE80211_CONF_CHANGE_RETRY_LIMITS) { + pr_debug("[STA] Retry limits: %d (long), %d (short).\n", + conf->long_frame_max_tx_count, + conf->short_frame_max_tx_count); + spin_lock_bh(&priv->tx_policy_cache.lock); + priv->long_frame_max_tx_count = conf->long_frame_max_tx_count; + priv->short_frame_max_tx_count = + (conf->short_frame_max_tx_count < 0x0F) ? + conf->short_frame_max_tx_count : 0x0F; + priv->hw->max_rate_tries = priv->short_frame_max_tx_count; + spin_unlock_bh(&priv->tx_policy_cache.lock); + } + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); + return ret; +} + +void cw1200_update_filtering(struct cw1200_common *priv) +{ + int ret; + bool bssid_filtering = !priv->rx_filter.bssid; + bool is_p2p = priv->vif && priv->vif->p2p; + bool is_sta = priv->vif && NL80211_IFTYPE_STATION == priv->vif->type; + + static struct wsm_beacon_filter_control bf_ctrl; + static struct wsm_mib_beacon_filter_table bf_tbl = { + .entry[0].ie_id = WLAN_EID_VENDOR_SPECIFIC, + .entry[0].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED | + WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT | + WSM_BEACON_FILTER_IE_HAS_APPEARED, + .entry[0].oui[0] = 0x50, + .entry[0].oui[1] = 0x6F, + .entry[0].oui[2] = 0x9A, + .entry[1].ie_id = WLAN_EID_HT_OPERATION, + .entry[1].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED | + WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT | + WSM_BEACON_FILTER_IE_HAS_APPEARED, + .entry[2].ie_id = WLAN_EID_ERP_INFO, + .entry[2].flags = WSM_BEACON_FILTER_IE_HAS_CHANGED | + WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT | + WSM_BEACON_FILTER_IE_HAS_APPEARED, + }; + + if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE) + return; + else if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) + bssid_filtering = false; + + if (priv->disable_beacon_filter) { + bf_ctrl.enabled = 0; + bf_ctrl.bcn_count = 1; + bf_tbl.num = __cpu_to_le32(0); + } else if (is_p2p || !is_sta) { + bf_ctrl.enabled = WSM_BEACON_FILTER_ENABLE | + WSM_BEACON_FILTER_AUTO_ERP; + bf_ctrl.bcn_count = 0; + bf_tbl.num = __cpu_to_le32(2); + } else { + bf_ctrl.enabled = WSM_BEACON_FILTER_ENABLE; + bf_ctrl.bcn_count = 0; + bf_tbl.num = __cpu_to_le32(3); + } + + /* When acting as p2p client being connected to p2p GO, in order to + * receive frames from a different p2p device, turn off bssid filter. + * + * WARNING: FW dependency! + * This can only be used with FW WSM371 and its successors. + * In that FW version even with bssid filter turned off, + * device will block most of the unwanted frames. + */ + if (is_p2p) + bssid_filtering = false; + + ret = wsm_set_rx_filter(priv, &priv->rx_filter); + if (!ret) + ret = wsm_set_beacon_filter_table(priv, &bf_tbl); + if (!ret) + ret = wsm_beacon_filter_control(priv, &bf_ctrl); + if (!ret) + ret = wsm_set_bssid_filtering(priv, bssid_filtering); + if (!ret) + ret = wsm_set_multicast_filter(priv, &priv->multicast_filter); + if (ret) + wiphy_err(priv->hw->wiphy, + "Update filtering failed: %d.\n", ret); + return; +} + +void cw1200_update_filtering_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, + update_filtering_work); + + cw1200_update_filtering(priv); +} + +void cw1200_set_beacon_wakeup_period_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, + set_beacon_wakeup_period_work); + + wsm_set_beacon_wakeup_period(priv, + priv->beacon_int * priv->join_dtim_period > + MAX_BEACON_SKIP_TIME_MS ? 1 : + priv->join_dtim_period, 0); +} + +u64 cw1200_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + static u8 broadcast_ipv6[ETH_ALEN] = { + 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 + }; + static u8 broadcast_ipv4[ETH_ALEN] = { + 0x01, 0x00, 0x5e, 0x00, 0x00, 0x01 + }; + struct cw1200_common *priv = hw->priv; + struct netdev_hw_addr *ha; + int count = 0; + + /* Disable multicast filtering */ + priv->has_multicast_subscription = false; + memset(&priv->multicast_filter, 0x00, sizeof(priv->multicast_filter)); + + if (netdev_hw_addr_list_count(mc_list) > WSM_MAX_GRP_ADDRTABLE_ENTRIES) + return 0; + + /* Enable if requested */ + netdev_hw_addr_list_for_each(ha, mc_list) { + pr_debug("[STA] multicast: %pM\n", ha->addr); + memcpy(&priv->multicast_filter.macaddrs[count], + ha->addr, ETH_ALEN); + if (!ether_addr_equal(ha->addr, broadcast_ipv4) && + !ether_addr_equal(ha->addr, broadcast_ipv6)) + priv->has_multicast_subscription = true; + count++; + } + + if (count) { + priv->multicast_filter.enable = __cpu_to_le32(1); + priv->multicast_filter.num_addrs = __cpu_to_le32(count); + } + + return netdev_hw_addr_list_count(mc_list); +} + +void cw1200_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast) +{ + struct cw1200_common *priv = dev->priv; + bool listening = !!(*total_flags & + (FIF_PROMISC_IN_BSS | + FIF_OTHER_BSS | + FIF_BCN_PRBRESP_PROMISC | + FIF_PROBE_REQ)); + + *total_flags &= FIF_PROMISC_IN_BSS | + FIF_OTHER_BSS | + FIF_FCSFAIL | + FIF_BCN_PRBRESP_PROMISC | + FIF_PROBE_REQ; + + down(&priv->scan.lock); + mutex_lock(&priv->conf_mutex); + + priv->rx_filter.promiscuous = (*total_flags & FIF_PROMISC_IN_BSS) + ? 1 : 0; + priv->rx_filter.bssid = (*total_flags & (FIF_OTHER_BSS | + FIF_PROBE_REQ)) ? 1 : 0; + priv->rx_filter.fcs = (*total_flags & FIF_FCSFAIL) ? 1 : 0; + priv->disable_beacon_filter = !(*total_flags & + (FIF_BCN_PRBRESP_PROMISC | + FIF_PROMISC_IN_BSS | + FIF_PROBE_REQ)); + if (priv->listening != listening) { + priv->listening = listening; + wsm_lock_tx(priv); + cw1200_update_listening(priv, listening); + wsm_unlock_tx(priv); + } + cw1200_update_filtering(priv); + mutex_unlock(&priv->conf_mutex); + up(&priv->scan.lock); +} + +int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params) +{ + struct cw1200_common *priv = dev->priv; + int ret = 0; + /* To prevent re-applying PM request OID again and again*/ + bool old_uapsd_flags; + + mutex_lock(&priv->conf_mutex); + + if (queue < dev->queues) { + old_uapsd_flags = le16_to_cpu(priv->uapsd_info.uapsd_flags); + + WSM_TX_QUEUE_SET(&priv->tx_queue_params, queue, 0, 0, 0); + ret = wsm_set_tx_queue_params(priv, + &priv->tx_queue_params.params[queue], queue); + if (ret) { + ret = -EINVAL; + goto out; + } + + WSM_EDCA_SET(&priv->edca, queue, params->aifs, + params->cw_min, params->cw_max, + params->txop, 0xc8, + params->uapsd); + ret = wsm_set_edca_params(priv, &priv->edca); + if (ret) { + ret = -EINVAL; + goto out; + } + + if (priv->mode == NL80211_IFTYPE_STATION) { + ret = cw1200_set_uapsd_param(priv, &priv->edca); + if (!ret && priv->setbssparams_done && + (priv->join_status == CW1200_JOIN_STATUS_STA) && + (old_uapsd_flags != le16_to_cpu(priv->uapsd_info.uapsd_flags))) + ret = cw1200_set_pm(priv, &priv->powersave_mode); + } + } else { + ret = -EINVAL; + } + +out: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +int cw1200_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats) +{ + struct cw1200_common *priv = dev->priv; + + memcpy(stats, &priv->stats, sizeof(*stats)); + return 0; +} + +int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg) +{ + struct wsm_set_pm pm = *arg; + + if (priv->uapsd_info.uapsd_flags != 0) + pm.mode &= ~WSM_PSM_FAST_PS_FLAG; + + if (memcmp(&pm, &priv->firmware_ps_mode, + sizeof(struct wsm_set_pm))) { + priv->firmware_ps_mode = pm; + return wsm_set_pm(priv, &pm); + } else { + return 0; + } +} + +int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + int ret = -EOPNOTSUPP; + struct cw1200_common *priv = dev->priv; + struct ieee80211_key_seq seq; + + mutex_lock(&priv->conf_mutex); + + if (cmd == SET_KEY) { + u8 *peer_addr = NULL; + int pairwise = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? + 1 : 0; + int idx = cw1200_alloc_key(priv); + struct wsm_add_key *wsm_key = &priv->keys[idx]; + + if (idx < 0) { + ret = -EINVAL; + goto finally; + } + + if (sta) + peer_addr = sta->addr; + + key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE | + IEEE80211_KEY_FLAG_RESERVE_TAILROOM; + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + case WLAN_CIPHER_SUITE_WEP104: + if (key->keylen > 16) { + cw1200_free_key(priv, idx); + ret = -EINVAL; + goto finally; + } + + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_WEP_PAIRWISE; + memcpy(wsm_key->wep_pairwise.peer, + peer_addr, ETH_ALEN); + memcpy(wsm_key->wep_pairwise.keydata, + &key->key[0], key->keylen); + wsm_key->wep_pairwise.keylen = key->keylen; + } else { + wsm_key->type = WSM_KEY_TYPE_WEP_DEFAULT; + memcpy(wsm_key->wep_group.keydata, + &key->key[0], key->keylen); + wsm_key->wep_group.keylen = key->keylen; + wsm_key->wep_group.keyid = key->keyidx; + } + break; + case WLAN_CIPHER_SUITE_TKIP: + ieee80211_get_key_rx_seq(key, 0, &seq); + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_TKIP_PAIRWISE; + memcpy(wsm_key->tkip_pairwise.peer, + peer_addr, ETH_ALEN); + memcpy(wsm_key->tkip_pairwise.keydata, + &key->key[0], 16); + memcpy(wsm_key->tkip_pairwise.tx_mic_key, + &key->key[16], 8); + memcpy(wsm_key->tkip_pairwise.rx_mic_key, + &key->key[24], 8); + } else { + size_t mic_offset = + (priv->mode == NL80211_IFTYPE_AP) ? + 16 : 24; + wsm_key->type = WSM_KEY_TYPE_TKIP_GROUP; + memcpy(wsm_key->tkip_group.keydata, + &key->key[0], 16); + memcpy(wsm_key->tkip_group.rx_mic_key, + &key->key[mic_offset], 8); + + wsm_key->tkip_group.rx_seqnum[0] = seq.tkip.iv16 & 0xff; + wsm_key->tkip_group.rx_seqnum[1] = (seq.tkip.iv16 >> 8) & 0xff; + wsm_key->tkip_group.rx_seqnum[2] = seq.tkip.iv32 & 0xff; + wsm_key->tkip_group.rx_seqnum[3] = (seq.tkip.iv32 >> 8) & 0xff; + wsm_key->tkip_group.rx_seqnum[4] = (seq.tkip.iv32 >> 16) & 0xff; + wsm_key->tkip_group.rx_seqnum[5] = (seq.tkip.iv32 >> 24) & 0xff; + wsm_key->tkip_group.rx_seqnum[6] = 0; + wsm_key->tkip_group.rx_seqnum[7] = 0; + + wsm_key->tkip_group.keyid = key->keyidx; + } + break; + case WLAN_CIPHER_SUITE_CCMP: + ieee80211_get_key_rx_seq(key, 0, &seq); + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_AES_PAIRWISE; + memcpy(wsm_key->aes_pairwise.peer, + peer_addr, ETH_ALEN); + memcpy(wsm_key->aes_pairwise.keydata, + &key->key[0], 16); + } else { + wsm_key->type = WSM_KEY_TYPE_AES_GROUP; + memcpy(wsm_key->aes_group.keydata, + &key->key[0], 16); + + wsm_key->aes_group.rx_seqnum[0] = seq.ccmp.pn[5]; + wsm_key->aes_group.rx_seqnum[1] = seq.ccmp.pn[4]; + wsm_key->aes_group.rx_seqnum[2] = seq.ccmp.pn[3]; + wsm_key->aes_group.rx_seqnum[3] = seq.ccmp.pn[2]; + wsm_key->aes_group.rx_seqnum[4] = seq.ccmp.pn[1]; + wsm_key->aes_group.rx_seqnum[5] = seq.ccmp.pn[0]; + wsm_key->aes_group.rx_seqnum[6] = 0; + wsm_key->aes_group.rx_seqnum[7] = 0; + wsm_key->aes_group.keyid = key->keyidx; + } + break; + case WLAN_CIPHER_SUITE_SMS4: + if (pairwise) { + wsm_key->type = WSM_KEY_TYPE_WAPI_PAIRWISE; + memcpy(wsm_key->wapi_pairwise.peer, + peer_addr, ETH_ALEN); + memcpy(wsm_key->wapi_pairwise.keydata, + &key->key[0], 16); + memcpy(wsm_key->wapi_pairwise.mic_key, + &key->key[16], 16); + wsm_key->wapi_pairwise.keyid = key->keyidx; + } else { + wsm_key->type = WSM_KEY_TYPE_WAPI_GROUP; + memcpy(wsm_key->wapi_group.keydata, + &key->key[0], 16); + memcpy(wsm_key->wapi_group.mic_key, + &key->key[16], 16); + wsm_key->wapi_group.keyid = key->keyidx; + } + break; + default: + pr_warn("Unhandled key type %d\n", key->cipher); + cw1200_free_key(priv, idx); + ret = -EOPNOTSUPP; + goto finally; + } + ret = wsm_add_key(priv, wsm_key); + if (!ret) + key->hw_key_idx = idx; + else + cw1200_free_key(priv, idx); + } else if (cmd == DISABLE_KEY) { + struct wsm_remove_key wsm_key = { + .index = key->hw_key_idx, + }; + + if (wsm_key.index > WSM_KEY_MAX_INDEX) { + ret = -EINVAL; + goto finally; + } + + cw1200_free_key(priv, wsm_key.index); + ret = wsm_remove_key(priv, &wsm_key); + } else { + pr_warn("Unhandled key command %d\n", cmd); + } + +finally: + mutex_unlock(&priv->conf_mutex); + return ret; +} + +void cw1200_wep_key_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, wep_key_work); + u8 queue_id = cw1200_queue_get_queue_id(priv->pending_frame_id); + struct cw1200_queue *queue = &priv->tx_queue[queue_id]; + __le32 wep_default_key_id = __cpu_to_le32( + priv->wep_default_key_id); + + pr_debug("[STA] Setting default WEP key: %d\n", + priv->wep_default_key_id); + wsm_flush_tx(priv); + wsm_write_mib(priv, WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID, + &wep_default_key_id, sizeof(wep_default_key_id)); + cw1200_queue_requeue(queue, priv->pending_frame_id); + wsm_unlock_tx(priv); +} + +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + int ret = 0; + __le32 val32; + struct cw1200_common *priv = hw->priv; + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) + return 0; + + if (value != (u32) -1) + val32 = __cpu_to_le32(value); + else + val32 = 0; /* disabled */ + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) { + /* device is down, can _not_ set threshold */ + ret = -ENODEV; + goto out; + } + + if (priv->rts_threshold == value) + goto out; + + pr_debug("[STA] Setting RTS threshold: %d\n", + priv->rts_threshold); + + /* mutex_lock(&priv->conf_mutex); */ + ret = wsm_write_mib(priv, WSM_MIB_ID_DOT11_RTS_THRESHOLD, + &val32, sizeof(val32)); + if (!ret) + priv->rts_threshold = value; + /* mutex_unlock(&priv->conf_mutex); */ + +out: + return ret; +} + +/* If successful, LOCKS the TX queue! */ +static int __cw1200_flush(struct cw1200_common *priv, bool drop) +{ + int i, ret; + + for (;;) { + /* TODO: correct flush handling is required when dev_stop. + * Temporary workaround: 2s + */ + if (drop) { + for (i = 0; i < 4; ++i) + cw1200_queue_clear(&priv->tx_queue[i]); + } else { + ret = wait_event_timeout( + priv->tx_queue_stats.wait_link_id_empty, + cw1200_queue_stats_is_empty( + &priv->tx_queue_stats, -1), + 2 * HZ); + } + + if (!drop && ret <= 0) { + ret = -ETIMEDOUT; + break; + } else { + ret = 0; + } + + wsm_lock_tx(priv); + if (!cw1200_queue_stats_is_empty(&priv->tx_queue_stats, -1)) { + /* Highly unlikely: WSM requeued frames. */ + wsm_unlock_tx(priv); + continue; + } + break; + } + return ret; +} + +void cw1200_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop) +{ + struct cw1200_common *priv = hw->priv; + + switch (priv->mode) { + case NL80211_IFTYPE_MONITOR: + drop = true; + break; + case NL80211_IFTYPE_AP: + if (!priv->enable_beacon) + drop = true; + break; + } + + if (!__cw1200_flush(priv, drop)) + wsm_unlock_tx(priv); + + return; +} + +/* ******************************************************************** */ +/* WSM callbacks */ + +void cw1200_free_event_queue(struct cw1200_common *priv) +{ + LIST_HEAD(list); + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + + __cw1200_free_event_queue(&list); +} + +void cw1200_event_handler(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, event_handler); + struct cw1200_wsm_event *event; + LIST_HEAD(list); + + spin_lock(&priv->event_queue_lock); + list_splice_init(&priv->event_queue, &list); + spin_unlock(&priv->event_queue_lock); + + list_for_each_entry(event, &list, link) { + switch (event->evt.id) { + case WSM_EVENT_ERROR: + pr_err("Unhandled WSM Error from LMAC\n"); + break; + case WSM_EVENT_BSS_LOST: + pr_debug("[CQM] BSS lost.\n"); + cancel_work_sync(&priv->unjoin_work); + if (!down_trylock(&priv->scan.lock)) { + cw1200_cqm_bssloss_sm(priv, 1, 0, 0); + up(&priv->scan.lock); + } else { + /* Scan is in progress. Delay reporting. + * Scan complete will trigger bss_loss_work + */ + priv->delayed_link_loss = 1; + /* Also start a watchdog. */ + queue_delayed_work(priv->workqueue, + &priv->bss_loss_work, 5*HZ); + } + break; + case WSM_EVENT_BSS_REGAINED: + pr_debug("[CQM] BSS regained.\n"); + cw1200_cqm_bssloss_sm(priv, 0, 0, 0); + cancel_work_sync(&priv->unjoin_work); + break; + case WSM_EVENT_RADAR_DETECTED: + wiphy_info(priv->hw->wiphy, "radar pulse detected\n"); + break; + case WSM_EVENT_RCPI_RSSI: + { + /* RSSI: signed Q8.0, RCPI: unsigned Q7.1 + * RSSI = RCPI / 2 - 110 + */ + int rcpi_rssi = (int)(event->evt.data & 0xFF); + int cqm_evt; + if (priv->cqm_use_rssi) + rcpi_rssi = (s8)rcpi_rssi; + else + rcpi_rssi = rcpi_rssi / 2 - 110; + + cqm_evt = (rcpi_rssi <= priv->cqm_rssi_thold) ? + NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW : + NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH; + pr_debug("[CQM] RSSI event: %d.\n", rcpi_rssi); + ieee80211_cqm_rssi_notify(priv->vif, cqm_evt, + GFP_KERNEL); + break; + } + case WSM_EVENT_BT_INACTIVE: + pr_warn("Unhandled BT INACTIVE from LMAC\n"); + break; + case WSM_EVENT_BT_ACTIVE: + pr_warn("Unhandled BT ACTIVE from LMAC\n"); + break; + } + } + __cw1200_free_event_queue(&list); +} + +void cw1200_bss_loss_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, bss_loss_work.work); + + pr_debug("[CQM] Reporting connection loss.\n"); + wsm_lock_tx(priv); + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); +} + +void cw1200_bss_params_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, bss_params_work); + mutex_lock(&priv->conf_mutex); + + priv->bss_params.reset_beacon_loss = 1; + wsm_set_bss_params(priv, &priv->bss_params); + priv->bss_params.reset_beacon_loss = 0; + + mutex_unlock(&priv->conf_mutex); +} + +/* ******************************************************************** */ +/* Internal API */ + +/* This function is called to Parse the SDD file + * to extract listen_interval and PTA related information + * sdd is a TLV: u8 id, u8 len, u8 data[] + */ +static int cw1200_parse_sdd_file(struct cw1200_common *priv) +{ + const u8 *p = priv->sdd->data; + int ret = 0; + + while (p + 2 <= priv->sdd->data + priv->sdd->size) { + if (p + p[1] + 2 > priv->sdd->data + priv->sdd->size) { + pr_warn("Malformed sdd structure\n"); + return -1; + } + switch (p[0]) { + case SDD_PTA_CFG_ELT_ID: { + u16 v; + if (p[1] < 4) { + pr_warn("SDD_PTA_CFG_ELT_ID malformed\n"); + ret = -1; + break; + } + v = le16_to_cpu(*((__le16 *)(p + 2))); + if (!v) /* non-zero means this is enabled */ + break; + + v = le16_to_cpu(*((__le16 *)(p + 4))); + priv->conf_listen_interval = (v >> 7) & 0x1F; + pr_debug("PTA found; Listen Interval %d\n", + priv->conf_listen_interval); + break; + } + case SDD_REFERENCE_FREQUENCY_ELT_ID: { + u16 clk = le16_to_cpu(*((__le16 *)(p + 2))); + if (clk != priv->hw_refclk) + pr_warn("SDD file doesn't match configured refclk (%d vs %d)\n", + clk, priv->hw_refclk); + break; + } + default: + break; + } + p += p[1] + 2; + } + + if (!priv->bt_present) { + pr_debug("PTA element NOT found.\n"); + priv->conf_listen_interval = 0; + } + return ret; +} + +int cw1200_setup_mac(struct cw1200_common *priv) +{ + int ret = 0; + + /* NOTE: There is a bug in FW: it reports signal + * as RSSI if RSSI subscription is enabled. + * It's not enough to set WSM_RCPI_RSSI_USE_RSSI. + * + * NOTE2: RSSI based reports have been switched to RCPI, since + * FW has a bug and RSSI reported values are not stable, + * what can leads to signal level oscilations in user-end applications + */ + struct wsm_rcpi_rssi_threshold threshold = { + .rssiRcpiMode = WSM_RCPI_RSSI_THRESHOLD_ENABLE | + WSM_RCPI_RSSI_DONT_USE_UPPER | + WSM_RCPI_RSSI_DONT_USE_LOWER, + .rollingAverageCount = 16, + }; + + struct wsm_configuration cfg = { + .dot11StationId = &priv->mac_addr[0], + }; + + /* Remember the decission here to make sure, we will handle + * the RCPI/RSSI value correctly on WSM_EVENT_RCPI_RSS + */ + if (threshold.rssiRcpiMode & WSM_RCPI_RSSI_USE_RSSI) + priv->cqm_use_rssi = true; + + if (!priv->sdd) { + ret = request_firmware(&priv->sdd, priv->sdd_path, priv->pdev); + if (ret) { + pr_err("Can't load sdd file %s.\n", priv->sdd_path); + return ret; + } + cw1200_parse_sdd_file(priv); + } + + cfg.dpdData = priv->sdd->data; + cfg.dpdData_size = priv->sdd->size; + ret = wsm_configuration(priv, &cfg); + if (ret) + return ret; + + /* Configure RSSI/SCPI reporting as RSSI. */ + wsm_set_rcpi_rssi_threshold(priv, &threshold); + + return 0; +} + +static void cw1200_join_complete(struct cw1200_common *priv) +{ + pr_debug("[STA] Join complete (%d)\n", priv->join_complete_status); + + priv->join_pending = false; + if (priv->join_complete_status) { + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + cw1200_update_listening(priv, priv->listening); + cw1200_do_unjoin(priv); + ieee80211_connection_loss(priv->vif); + } else { + if (priv->mode == NL80211_IFTYPE_ADHOC) + priv->join_status = CW1200_JOIN_STATUS_IBSS; + else + priv->join_status = CW1200_JOIN_STATUS_PRE_STA; + } + wsm_unlock_tx(priv); /* Clearing the lock held before do_join() */ +} + +void cw1200_join_complete_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, join_complete_work); + mutex_lock(&priv->conf_mutex); + cw1200_join_complete(priv); + mutex_unlock(&priv->conf_mutex); +} + +void cw1200_join_complete_cb(struct cw1200_common *priv, + struct wsm_join_complete *arg) +{ + pr_debug("[STA] cw1200_join_complete_cb called, status=%d.\n", + arg->status); + + if (cancel_delayed_work(&priv->join_timeout)) { + priv->join_complete_status = arg->status; + queue_work(priv->workqueue, &priv->join_complete_work); + } +} + +/* MUST be called with tx_lock held! It will be unlocked for us. */ +static void cw1200_do_join(struct cw1200_common *priv) +{ + const u8 *bssid; + struct ieee80211_bss_conf *conf = &priv->vif->bss_conf; + struct cfg80211_bss *bss = NULL; + struct wsm_protected_mgmt_policy mgmt_policy; + struct wsm_join join = { + .mode = conf->ibss_joined ? + WSM_JOIN_MODE_IBSS : WSM_JOIN_MODE_BSS, + .preamble_type = WSM_JOIN_PREAMBLE_LONG, + .probe_for_join = 1, + .atim_window = 0, + .basic_rate_set = cw1200_rate_mask_to_wsm(priv, + conf->basic_rates), + }; + if (delayed_work_pending(&priv->join_timeout)) { + pr_warn("[STA] - Join request already pending, skipping..\n"); + wsm_unlock_tx(priv); + return; + } + + if (priv->join_status) + cw1200_do_unjoin(priv); + + bssid = priv->vif->bss_conf.bssid; + + bss = cfg80211_get_bss(priv->hw->wiphy, priv->channel, bssid, NULL, 0, + IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); + + if (!bss && !conf->ibss_joined) { + wsm_unlock_tx(priv); + return; + } + + mutex_lock(&priv->conf_mutex); + + /* Under the conf lock: check scan status and + * bail out if it is in progress. + */ + if (atomic_read(&priv->scan.in_progress)) { + wsm_unlock_tx(priv); + goto done_put; + } + + priv->join_pending = true; + + /* Sanity check basic rates */ + if (!join.basic_rate_set) + join.basic_rate_set = 7; + + /* Sanity check beacon interval */ + if (!priv->beacon_int) + priv->beacon_int = 1; + + join.beacon_interval = priv->beacon_int; + + /* BT Coex related changes */ + if (priv->bt_present) { + if (((priv->conf_listen_interval * 100) % + priv->beacon_int) == 0) + priv->listen_interval = + ((priv->conf_listen_interval * 100) / + priv->beacon_int); + else + priv->listen_interval = + ((priv->conf_listen_interval * 100) / + priv->beacon_int + 1); + } + + if (priv->hw->conf.ps_dtim_period) + priv->join_dtim_period = priv->hw->conf.ps_dtim_period; + join.dtim_period = priv->join_dtim_period; + + join.channel_number = priv->channel->hw_value; + join.band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G; + + memcpy(join.bssid, bssid, sizeof(join.bssid)); + + pr_debug("[STA] Join BSSID: %pM DTIM: %d, interval: %d\n", + join.bssid, + join.dtim_period, priv->beacon_int); + + if (!conf->ibss_joined) { + const u8 *ssidie; + rcu_read_lock(); + ssidie = ieee80211_bss_get_ie(bss, WLAN_EID_SSID); + if (ssidie) { + join.ssid_len = ssidie[1]; + memcpy(join.ssid, &ssidie[2], join.ssid_len); + } + rcu_read_unlock(); + } + + if (priv->vif->p2p) { + join.flags |= WSM_JOIN_FLAGS_P2P_GO; + join.basic_rate_set = + cw1200_rate_mask_to_wsm(priv, 0xFF0); + } + + /* Enable asynchronous join calls */ + if (!conf->ibss_joined) { + join.flags |= WSM_JOIN_FLAGS_FORCE; + join.flags |= WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND; + } + + wsm_flush_tx(priv); + + /* Stay Awake for Join and Auth Timeouts and a bit more */ + cw1200_pm_stay_awake(&priv->pm_state, + CW1200_JOIN_TIMEOUT + CW1200_AUTH_TIMEOUT); + + cw1200_update_listening(priv, false); + + /* Turn on Block ACKs */ + wsm_set_block_ack_policy(priv, priv->ba_tx_tid_mask, + priv->ba_rx_tid_mask); + + /* Set up timeout */ + if (join.flags & WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND) { + priv->join_status = CW1200_JOIN_STATUS_JOINING; + queue_delayed_work(priv->workqueue, + &priv->join_timeout, + CW1200_JOIN_TIMEOUT); + } + + /* 802.11w protected mgmt frames */ + mgmt_policy.protectedMgmtEnable = 0; + mgmt_policy.unprotectedMgmtFramesAllowed = 1; + mgmt_policy.encryptionForAuthFrame = 1; + wsm_set_protected_mgmt_policy(priv, &mgmt_policy); + + /* Perform actual join */ + if (wsm_join(priv, &join)) { + pr_err("[STA] cw1200_join_work: wsm_join failed!\n"); + cancel_delayed_work_sync(&priv->join_timeout); + cw1200_update_listening(priv, priv->listening); + /* Tx lock still held, unjoin will clear it. */ + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } else { + if (!(join.flags & WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND)) + cw1200_join_complete(priv); /* Will clear tx_lock */ + + /* Upload keys */ + cw1200_upload_keys(priv); + + /* Due to beacon filtering it is possible that the + * AP's beacon is not known for the mac80211 stack. + * Disable filtering temporary to make sure the stack + * receives at least one + */ + priv->disable_beacon_filter = true; + } + cw1200_update_filtering(priv); + +done_put: + mutex_unlock(&priv->conf_mutex); + if (bss) + cfg80211_put_bss(priv->hw->wiphy, bss); +} + +void cw1200_join_timeout(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, join_timeout.work); + pr_debug("[WSM] Join timed out.\n"); + wsm_lock_tx(priv); + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); +} + +static void cw1200_do_unjoin(struct cw1200_common *priv) +{ + struct wsm_reset reset = { + .reset_statistics = true, + }; + + cancel_delayed_work_sync(&priv->join_timeout); + + mutex_lock(&priv->conf_mutex); + priv->join_pending = false; + + if (atomic_read(&priv->scan.in_progress)) { + if (priv->delayed_unjoin) + wiphy_dbg(priv->hw->wiphy, "Delayed unjoin is already scheduled.\n"); + else + priv->delayed_unjoin = true; + goto done; + } + + priv->delayed_link_loss = false; + + if (!priv->join_status) + goto done; + + if (priv->join_status == CW1200_JOIN_STATUS_AP) + goto done; + + cancel_work_sync(&priv->update_filtering_work); + cancel_work_sync(&priv->set_beacon_wakeup_period_work); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + + /* Unjoin is a reset. */ + wsm_flush_tx(priv); + wsm_keep_alive_period(priv, 0); + wsm_reset(priv, &reset); + wsm_set_output_power(priv, priv->output_power * 10); + priv->join_dtim_period = 0; + cw1200_setup_mac(priv); + cw1200_free_event_queue(priv); + cancel_work_sync(&priv->event_handler); + cw1200_update_listening(priv, priv->listening); + cw1200_cqm_bssloss_sm(priv, 0, 0, 0); + + /* Disable Block ACKs */ + wsm_set_block_ack_policy(priv, 0, 0); + + priv->disable_beacon_filter = false; + cw1200_update_filtering(priv); + memset(&priv->association_mode, 0, + sizeof(priv->association_mode)); + memset(&priv->bss_params, 0, sizeof(priv->bss_params)); + priv->setbssparams_done = false; + memset(&priv->firmware_ps_mode, 0, + sizeof(priv->firmware_ps_mode)); + + pr_debug("[STA] Unjoin completed.\n"); + +done: + mutex_unlock(&priv->conf_mutex); +} + +void cw1200_unjoin_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, unjoin_work); + + cw1200_do_unjoin(priv); + + /* Tell the stack we're dead */ + ieee80211_connection_loss(priv->vif); + + wsm_unlock_tx(priv); +} + +int cw1200_enable_listening(struct cw1200_common *priv) +{ + struct wsm_start start = { + .mode = WSM_START_MODE_P2P_DEV, + .band = WSM_PHY_BAND_2_4G, + .beacon_interval = 100, + .dtim_period = 1, + .probe_delay = 0, + .basic_rate_set = 0x0F, + }; + + if (priv->channel) { + start.band = priv->channel->band == IEEE80211_BAND_5GHZ ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G; + start.channel_number = priv->channel->hw_value; + } else { + start.band = WSM_PHY_BAND_2_4G; + start.channel_number = 1; + } + + return wsm_start(priv, &start); +} + +int cw1200_disable_listening(struct cw1200_common *priv) +{ + int ret; + struct wsm_reset reset = { + .reset_statistics = true, + }; + ret = wsm_reset(priv, &reset); + return ret; +} + +void cw1200_update_listening(struct cw1200_common *priv, bool enabled) +{ + if (enabled) { + if (priv->join_status == CW1200_JOIN_STATUS_PASSIVE) { + if (!cw1200_enable_listening(priv)) + priv->join_status = CW1200_JOIN_STATUS_MONITOR; + wsm_set_probe_responder(priv, true); + } + } else { + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) { + if (!cw1200_disable_listening(priv)) + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + wsm_set_probe_responder(priv, false); + } + } +} + +int cw1200_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg) +{ + int ret; + u16 uapsd_flags = 0; + + /* Here's the mapping AC [queue, bit] + * VO [0,3], VI [1, 2], BE [2, 1], BK [3, 0] + */ + + if (arg->uapsd_enable[0]) + uapsd_flags |= 1 << 3; + + if (arg->uapsd_enable[1]) + uapsd_flags |= 1 << 2; + + if (arg->uapsd_enable[2]) + uapsd_flags |= 1 << 1; + + if (arg->uapsd_enable[3]) + uapsd_flags |= 1; + + /* Currently pseudo U-APSD operation is not supported, so setting + * MinAutoTriggerInterval, MaxAutoTriggerInterval and + * AutoTriggerStep to 0 + */ + + priv->uapsd_info.uapsd_flags = cpu_to_le16(uapsd_flags); + priv->uapsd_info.min_auto_trigger_interval = 0; + priv->uapsd_info.max_auto_trigger_interval = 0; + priv->uapsd_info.auto_trigger_step = 0; + + ret = wsm_set_uapsd_info(priv, &priv->uapsd_info); + return ret; +} + +/* ******************************************************************** */ +/* AP API */ + +int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + struct cw1200_link_entry *entry; + struct sk_buff *skb; + + if (priv->mode != NL80211_IFTYPE_AP) + return 0; + + sta_priv->link_id = cw1200_find_link_id(priv, sta->addr); + if (WARN_ON(!sta_priv->link_id)) { + wiphy_info(priv->hw->wiphy, + "[AP] No more link IDs available.\n"); + return -ENOENT; + } + + entry = &priv->link_id_db[sta_priv->link_id - 1]; + spin_lock_bh(&priv->ps_state_lock); + if ((sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) == + IEEE80211_WMM_IE_STA_QOSINFO_AC_MASK) + priv->sta_asleep_mask |= BIT(sta_priv->link_id); + entry->status = CW1200_LINK_HARD; + while ((skb = skb_dequeue(&entry->rx_queue))) + ieee80211_rx_irqsafe(priv->hw, skb); + spin_unlock_bh(&priv->ps_state_lock); + return 0; +} + +int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = hw->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + struct cw1200_link_entry *entry; + + if (priv->mode != NL80211_IFTYPE_AP || !sta_priv->link_id) + return 0; + + entry = &priv->link_id_db[sta_priv->link_id - 1]; + spin_lock_bh(&priv->ps_state_lock); + entry->status = CW1200_LINK_RESERVE; + entry->timestamp = jiffies; + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + spin_unlock_bh(&priv->ps_state_lock); + flush_workqueue(priv->workqueue); + return 0; +} + +static void __cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + int link_id) +{ + struct cw1200_common *priv = dev->priv; + u32 bit, prev; + + /* Zero link id means "for all link IDs" */ + if (link_id) + bit = BIT(link_id); + else if (WARN_ON_ONCE(notify_cmd != STA_NOTIFY_AWAKE)) + bit = 0; + else + bit = priv->link_id_map; + prev = priv->sta_asleep_mask & bit; + + switch (notify_cmd) { + case STA_NOTIFY_SLEEP: + if (!prev) { + if (priv->buffered_multicasts && + !priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_start_work); + priv->sta_asleep_mask |= bit; + } + break; + case STA_NOTIFY_AWAKE: + if (prev) { + priv->sta_asleep_mask &= ~bit; + priv->pspoll_mask &= ~bit; + if (priv->tx_multicast && link_id && + !priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_stop_work); + cw1200_bh_wakeup(priv); + } + break; + } +} + +void cw1200_sta_notify(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta) +{ + struct cw1200_common *priv = dev->priv; + struct cw1200_sta_priv *sta_priv = + (struct cw1200_sta_priv *)&sta->drv_priv; + + spin_lock_bh(&priv->ps_state_lock); + __cw1200_sta_notify(dev, vif, notify_cmd, sta_priv->link_id); + spin_unlock_bh(&priv->ps_state_lock); +} + +static void cw1200_ps_notify(struct cw1200_common *priv, + int link_id, bool ps) +{ + if (link_id > CW1200_MAX_STA_IN_AP_MODE) + return; + + pr_debug("%s for LinkId: %d. STAs asleep: %.8X\n", + ps ? "Stop" : "Start", + link_id, priv->sta_asleep_mask); + + __cw1200_sta_notify(priv->hw, priv->vif, + ps ? STA_NOTIFY_SLEEP : STA_NOTIFY_AWAKE, link_id); +} + +static int cw1200_set_tim_impl(struct cw1200_common *priv, bool aid0_bit_set) +{ + struct sk_buff *skb; + struct wsm_update_ie update_ie = { + .what = WSM_UPDATE_IE_BEACON, + .count = 1, + }; + u16 tim_offset, tim_length; + + pr_debug("[AP] mcast: %s.\n", aid0_bit_set ? "ena" : "dis"); + + skb = ieee80211_beacon_get_tim(priv->hw, priv->vif, + &tim_offset, &tim_length); + if (!skb) { + if (!__cw1200_flush(priv, true)) + wsm_unlock_tx(priv); + return -ENOENT; + } + + if (tim_offset && tim_length >= 6) { + /* Ignore DTIM count from mac80211: + * firmware handles DTIM internally. + */ + skb->data[tim_offset + 2] = 0; + + /* Set/reset aid0 bit */ + if (aid0_bit_set) + skb->data[tim_offset + 4] |= 1; + else + skb->data[tim_offset + 4] &= ~1; + } + + update_ie.ies = &skb->data[tim_offset]; + update_ie.length = tim_length; + wsm_update_ie(priv, &update_ie); + + dev_kfree_skb(skb); + + return 0; +} + +void cw1200_set_tim_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, set_tim_work); + (void)cw1200_set_tim_impl(priv, priv->aid0_bit_set); +} + +int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta, + bool set) +{ + struct cw1200_common *priv = dev->priv; + queue_work(priv->workqueue, &priv->set_tim_work); + return 0; +} + +void cw1200_set_cts_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, set_cts_work); + + u8 erp_ie[3] = {WLAN_EID_ERP_INFO, 0x1, 0}; + struct wsm_update_ie update_ie = { + .what = WSM_UPDATE_IE_BEACON, + .count = 1, + .ies = erp_ie, + .length = 3, + }; + u32 erp_info; + __le32 use_cts_prot; + mutex_lock(&priv->conf_mutex); + erp_info = priv->erp_info; + mutex_unlock(&priv->conf_mutex); + use_cts_prot = + erp_info & WLAN_ERP_USE_PROTECTION ? + __cpu_to_le32(1) : 0; + + erp_ie[ERP_INFO_BYTE_OFFSET] = erp_info; + + pr_debug("[STA] ERP information 0x%x\n", erp_info); + + wsm_write_mib(priv, WSM_MIB_ID_NON_ERP_PROTECTION, + &use_cts_prot, sizeof(use_cts_prot)); + wsm_update_ie(priv, &update_ie); + + return; +} + +static int cw1200_set_btcoexinfo(struct cw1200_common *priv) +{ + struct wsm_override_internal_txrate arg; + int ret = 0; + + if (priv->mode == NL80211_IFTYPE_STATION) { + /* Plumb PSPOLL and NULL template */ + cw1200_upload_pspoll(priv); + cw1200_upload_null(priv); + cw1200_upload_qosnull(priv); + } else { + return 0; + } + + memset(&arg, 0, sizeof(struct wsm_override_internal_txrate)); + + if (!priv->vif->p2p) { + /* STATION mode */ + if (priv->bss_params.operational_rate_set & ~0xF) { + pr_debug("[STA] STA has ERP rates\n"); + /* G or BG mode */ + arg.internalTxRate = (__ffs( + priv->bss_params.operational_rate_set & ~0xF)); + } else { + pr_debug("[STA] STA has non ERP rates\n"); + /* B only mode */ + arg.internalTxRate = (__ffs(le32_to_cpu(priv->association_mode.basic_rate_set))); + } + arg.nonErpInternalTxRate = (__ffs(le32_to_cpu(priv->association_mode.basic_rate_set))); + } else { + /* P2P mode */ + arg.internalTxRate = (__ffs(priv->bss_params.operational_rate_set & ~0xF)); + arg.nonErpInternalTxRate = (__ffs(priv->bss_params.operational_rate_set & ~0xF)); + } + + pr_debug("[STA] BTCOEX_INFO MODE %d, internalTxRate : %x, nonErpInternalTxRate: %x\n", + priv->mode, + arg.internalTxRate, + arg.nonErpInternalTxRate); + + ret = wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE, + &arg, sizeof(arg)); + + return ret; +} + +void cw1200_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed) +{ + struct cw1200_common *priv = dev->priv; + bool do_join = false; + + mutex_lock(&priv->conf_mutex); + + pr_debug("BSS CHANGED: %08x\n", changed); + + /* TODO: BSS_CHANGED_QOS */ + /* TODO: BSS_CHANGED_TXPOWER */ + + if (changed & BSS_CHANGED_ARP_FILTER) { + struct wsm_mib_arp_ipv4_filter filter = {0}; + int i; + + pr_debug("[STA] BSS_CHANGED_ARP_FILTER cnt: %d\n", + info->arp_addr_cnt); + + /* Currently only one IP address is supported by firmware. + * In case of more IPs arp filtering will be disabled. + */ + if (info->arp_addr_cnt > 0 && + info->arp_addr_cnt <= WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES) { + for (i = 0; i < info->arp_addr_cnt; i++) { + filter.ipv4addrs[i] = info->arp_addr_list[i]; + pr_debug("[STA] addr[%d]: 0x%X\n", + i, filter.ipv4addrs[i]); + } + filter.enable = __cpu_to_le32(1); + } + + pr_debug("[STA] arp ip filter enable: %d\n", + __le32_to_cpu(filter.enable)); + + wsm_set_arp_ipv4_filter(priv, &filter); + } + + if (changed & + (BSS_CHANGED_BEACON | + BSS_CHANGED_AP_PROBE_RESP | + BSS_CHANGED_BSSID | + BSS_CHANGED_SSID | + BSS_CHANGED_IBSS)) { + pr_debug("BSS_CHANGED_BEACON\n"); + priv->beacon_int = info->beacon_int; + cw1200_update_beaconing(priv); + cw1200_upload_beacon(priv); + } + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + pr_debug("BSS_CHANGED_BEACON_ENABLED (%d)\n", info->enable_beacon); + + if (priv->enable_beacon != info->enable_beacon) { + cw1200_enable_beaconing(priv, info->enable_beacon); + priv->enable_beacon = info->enable_beacon; + } + } + + if (changed & BSS_CHANGED_BEACON_INT) { + pr_debug("CHANGED_BEACON_INT\n"); + if (info->ibss_joined) + do_join = true; + else if (priv->join_status == CW1200_JOIN_STATUS_AP) + cw1200_update_beaconing(priv); + } + + /* assoc/disassoc, or maybe AID changed */ + if (changed & BSS_CHANGED_ASSOC) { + wsm_lock_tx(priv); + priv->wep_default_key_id = -1; + wsm_unlock_tx(priv); + } + + if (changed & BSS_CHANGED_BSSID) { + pr_debug("BSS_CHANGED_BSSID\n"); + do_join = true; + } + + if (changed & + (BSS_CHANGED_ASSOC | + BSS_CHANGED_BSSID | + BSS_CHANGED_IBSS | + BSS_CHANGED_BASIC_RATES | + BSS_CHANGED_HT)) { + pr_debug("BSS_CHANGED_ASSOC\n"); + if (info->assoc) { + if (priv->join_status < CW1200_JOIN_STATUS_PRE_STA) { + ieee80211_connection_loss(vif); + mutex_unlock(&priv->conf_mutex); + return; + } else if (priv->join_status == CW1200_JOIN_STATUS_PRE_STA) { + priv->join_status = CW1200_JOIN_STATUS_STA; + } + } else { + do_join = true; + } + + if (info->assoc || info->ibss_joined) { + struct ieee80211_sta *sta = NULL; + __le32 htprot = 0; + + if (info->dtim_period) + priv->join_dtim_period = info->dtim_period; + priv->beacon_int = info->beacon_int; + + rcu_read_lock(); + + if (info->bssid && !info->ibss_joined) + sta = ieee80211_find_sta(vif, info->bssid); + if (sta) { + priv->ht_info.ht_cap = sta->ht_cap; + priv->bss_params.operational_rate_set = + cw1200_rate_mask_to_wsm(priv, + sta->supp_rates[priv->channel->band]); + priv->ht_info.channel_type = cfg80211_get_chandef_type(&dev->conf.chandef); + priv->ht_info.operation_mode = info->ht_operation_mode; + } else { + memset(&priv->ht_info, 0, + sizeof(priv->ht_info)); + priv->bss_params.operational_rate_set = -1; + } + rcu_read_unlock(); + + /* Non Greenfield stations present */ + if (priv->ht_info.operation_mode & + IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) + htprot |= cpu_to_le32(WSM_NON_GREENFIELD_STA_PRESENT); + + /* Set HT protection method */ + htprot |= cpu_to_le32((priv->ht_info.operation_mode & IEEE80211_HT_OP_MODE_PROTECTION) << 2); + + /* TODO: + * STBC_param.dual_cts + * STBC_param.LSIG_TXOP_FILL + */ + + wsm_write_mib(priv, WSM_MIB_ID_SET_HT_PROTECTION, + &htprot, sizeof(htprot)); + + priv->association_mode.greenfield = + cw1200_ht_greenfield(&priv->ht_info); + priv->association_mode.flags = + WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES | + WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE | + WSM_ASSOCIATION_MODE_USE_HT_MODE | + WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET | + WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING; + priv->association_mode.preamble = + info->use_short_preamble ? + WSM_JOIN_PREAMBLE_SHORT : + WSM_JOIN_PREAMBLE_LONG; + priv->association_mode.basic_rate_set = __cpu_to_le32( + cw1200_rate_mask_to_wsm(priv, + info->basic_rates)); + priv->association_mode.mpdu_start_spacing = + cw1200_ht_ampdu_density(&priv->ht_info); + + cw1200_cqm_bssloss_sm(priv, 0, 0, 0); + cancel_work_sync(&priv->unjoin_work); + + priv->bss_params.beacon_lost_count = priv->cqm_beacon_loss_count; + priv->bss_params.aid = info->aid; + + if (priv->join_dtim_period < 1) + priv->join_dtim_period = 1; + + pr_debug("[STA] DTIM %d, interval: %d\n", + priv->join_dtim_period, priv->beacon_int); + pr_debug("[STA] Preamble: %d, Greenfield: %d, Aid: %d, Rates: 0x%.8X, Basic: 0x%.8X\n", + priv->association_mode.preamble, + priv->association_mode.greenfield, + priv->bss_params.aid, + priv->bss_params.operational_rate_set, + priv->association_mode.basic_rate_set); + wsm_set_association_mode(priv, &priv->association_mode); + + if (!info->ibss_joined) { + wsm_keep_alive_period(priv, 30 /* sec */); + wsm_set_bss_params(priv, &priv->bss_params); + priv->setbssparams_done = true; + cw1200_set_beacon_wakeup_period_work(&priv->set_beacon_wakeup_period_work); + cw1200_set_pm(priv, &priv->powersave_mode); + } + if (priv->vif->p2p) { + pr_debug("[STA] Setting p2p powersave configuration.\n"); + wsm_set_p2p_ps_modeinfo(priv, + &priv->p2p_ps_modeinfo); + } + if (priv->bt_present) + cw1200_set_btcoexinfo(priv); + } else { + memset(&priv->association_mode, 0, + sizeof(priv->association_mode)); + memset(&priv->bss_params, 0, sizeof(priv->bss_params)); + } + } + + /* ERP Protection */ + if (changed & (BSS_CHANGED_ASSOC | + BSS_CHANGED_ERP_CTS_PROT | + BSS_CHANGED_ERP_PREAMBLE)) { + u32 prev_erp_info = priv->erp_info; + if (info->use_cts_prot) + priv->erp_info |= WLAN_ERP_USE_PROTECTION; + else if (!(prev_erp_info & WLAN_ERP_NON_ERP_PRESENT)) + priv->erp_info &= ~WLAN_ERP_USE_PROTECTION; + + if (info->use_short_preamble) + priv->erp_info |= WLAN_ERP_BARKER_PREAMBLE; + else + priv->erp_info &= ~WLAN_ERP_BARKER_PREAMBLE; + + pr_debug("[STA] ERP Protection: %x\n", priv->erp_info); + + if (prev_erp_info != priv->erp_info) + queue_work(priv->workqueue, &priv->set_cts_work); + } + + /* ERP Slottime */ + if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_ERP_SLOT)) { + __le32 slot_time = info->use_short_slot ? + __cpu_to_le32(9) : __cpu_to_le32(20); + pr_debug("[STA] Slot time: %d us.\n", + __le32_to_cpu(slot_time)); + wsm_write_mib(priv, WSM_MIB_ID_DOT11_SLOT_TIME, + &slot_time, sizeof(slot_time)); + } + + if (changed & (BSS_CHANGED_ASSOC | BSS_CHANGED_CQM)) { + struct wsm_rcpi_rssi_threshold threshold = { + .rollingAverageCount = 8, + }; + pr_debug("[CQM] RSSI threshold subscribe: %d +- %d\n", + info->cqm_rssi_thold, info->cqm_rssi_hyst); + priv->cqm_rssi_thold = info->cqm_rssi_thold; + priv->cqm_rssi_hyst = info->cqm_rssi_hyst; + + if (info->cqm_rssi_thold || info->cqm_rssi_hyst) { + /* RSSI subscription enabled */ + /* TODO: It's not a correct way of setting threshold. + * Upper and lower must be set equal here and adjusted + * in callback. However current implementation is much + * more relaible and stable. + */ + + /* RSSI: signed Q8.0, RCPI: unsigned Q7.1 + * RSSI = RCPI / 2 - 110 + */ + if (priv->cqm_use_rssi) { + threshold.upperThreshold = + info->cqm_rssi_thold + info->cqm_rssi_hyst; + threshold.lowerThreshold = + info->cqm_rssi_thold; + threshold.rssiRcpiMode |= WSM_RCPI_RSSI_USE_RSSI; + } else { + threshold.upperThreshold = (info->cqm_rssi_thold + info->cqm_rssi_hyst + 110) * 2; + threshold.lowerThreshold = (info->cqm_rssi_thold + 110) * 2; + } + threshold.rssiRcpiMode |= WSM_RCPI_RSSI_THRESHOLD_ENABLE; + } else { + /* There is a bug in FW, see sta.c. We have to enable + * dummy subscription to get correct RSSI values. + */ + threshold.rssiRcpiMode |= + WSM_RCPI_RSSI_THRESHOLD_ENABLE | + WSM_RCPI_RSSI_DONT_USE_UPPER | + WSM_RCPI_RSSI_DONT_USE_LOWER; + if (priv->cqm_use_rssi) + threshold.rssiRcpiMode |= WSM_RCPI_RSSI_USE_RSSI; + } + wsm_set_rcpi_rssi_threshold(priv, &threshold); + } + mutex_unlock(&priv->conf_mutex); + + if (do_join) { + wsm_lock_tx(priv); + cw1200_do_join(priv); /* Will unlock it for us */ + } +} + +void cw1200_multicast_start_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, multicast_start_work); + long tmo = priv->join_dtim_period * + (priv->beacon_int + 20) * HZ / 1024; + + cancel_work_sync(&priv->multicast_stop_work); + + if (!priv->aid0_bit_set) { + wsm_lock_tx(priv); + cw1200_set_tim_impl(priv, true); + priv->aid0_bit_set = true; + mod_timer(&priv->mcast_timeout, jiffies + tmo); + wsm_unlock_tx(priv); + } +} + +void cw1200_multicast_stop_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, multicast_stop_work); + + if (priv->aid0_bit_set) { + del_timer_sync(&priv->mcast_timeout); + wsm_lock_tx(priv); + priv->aid0_bit_set = false; + cw1200_set_tim_impl(priv, false); + wsm_unlock_tx(priv); + } +} + +void cw1200_mcast_timeout(unsigned long arg) +{ + struct cw1200_common *priv = + (struct cw1200_common *)arg; + + wiphy_warn(priv->hw->wiphy, + "Multicast delivery timeout.\n"); + spin_lock_bh(&priv->ps_state_lock); + priv->tx_multicast = priv->aid0_bit_set && + priv->buffered_multicasts; + if (priv->tx_multicast) + cw1200_bh_wakeup(priv); + spin_unlock_bh(&priv->ps_state_lock); +} + +int cw1200_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size) +{ + /* Aggregation is implemented fully in firmware, + * including block ack negotiation. Do not allow + * mac80211 stack to do anything: it interferes with + * the firmware. + */ + + /* Note that we still need this function stubbed. */ + return -ENOTSUPP; +} + +/* ******************************************************************** */ +/* WSM callback */ +void cw1200_suspend_resume(struct cw1200_common *priv, + struct wsm_suspend_resume *arg) +{ + pr_debug("[AP] %s: %s\n", + arg->stop ? "stop" : "start", + arg->multicast ? "broadcast" : "unicast"); + + if (arg->multicast) { + bool cancel_tmo = false; + spin_lock_bh(&priv->ps_state_lock); + if (arg->stop) { + priv->tx_multicast = false; + } else { + /* Firmware sends this indication every DTIM if there + * is a STA in powersave connected. There is no reason + * to suspend, following wakeup will consume much more + * power than it could be saved. + */ + cw1200_pm_stay_awake(&priv->pm_state, + priv->join_dtim_period * + (priv->beacon_int + 20) * HZ / 1024); + priv->tx_multicast = (priv->aid0_bit_set && + priv->buffered_multicasts); + if (priv->tx_multicast) { + cancel_tmo = true; + cw1200_bh_wakeup(priv); + } + } + spin_unlock_bh(&priv->ps_state_lock); + if (cancel_tmo) + del_timer_sync(&priv->mcast_timeout); + } else { + spin_lock_bh(&priv->ps_state_lock); + cw1200_ps_notify(priv, arg->link_id, arg->stop); + spin_unlock_bh(&priv->ps_state_lock); + if (!arg->stop) + cw1200_bh_wakeup(priv); + } + return; +} + +/* ******************************************************************** */ +/* AP privates */ + +static int cw1200_upload_beacon(struct cw1200_common *priv) +{ + int ret = 0; + struct ieee80211_mgmt *mgmt; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_BEACON, + }; + + u16 tim_offset; + u16 tim_len; + + if (priv->mode == NL80211_IFTYPE_STATION || + priv->mode == NL80211_IFTYPE_MONITOR || + priv->mode == NL80211_IFTYPE_UNSPECIFIED) + goto done; + + if (priv->vif->p2p) + frame.rate = WSM_TRANSMIT_RATE_6; + + frame.skb = ieee80211_beacon_get_tim(priv->hw, priv->vif, + &tim_offset, &tim_len); + if (!frame.skb) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + if (ret) + goto done; + + /* TODO: Distill probe resp; remove TIM + * and any other beacon-specific IEs + */ + mgmt = (void *)frame.skb->data; + mgmt->frame_control = + __cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_PROBE_RESP); + + frame.frame_type = WSM_FRAME_TYPE_PROBE_RESPONSE; + if (priv->vif->p2p) { + ret = wsm_set_probe_responder(priv, true); + } else { + ret = wsm_set_template_frame(priv, &frame); + wsm_set_probe_responder(priv, false); + } + +done: + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_upload_pspoll(struct cw1200_common *priv) +{ + int ret = 0; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_PS_POLL, + .rate = 0xFF, + }; + + + frame.skb = ieee80211_pspoll_get(priv->hw, priv->vif); + if (!frame.skb) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_upload_null(struct cw1200_common *priv) +{ + int ret = 0; + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_NULL, + .rate = 0xFF, + }; + + frame.skb = ieee80211_nullfunc_get(priv->hw, priv->vif); + if (!frame.skb) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + dev_kfree_skb(frame.skb); + + return ret; +} + +static int cw1200_upload_qosnull(struct cw1200_common *priv) +{ + /* TODO: This needs to be implemented + + struct wsm_template_frame frame = { + .frame_type = WSM_FRAME_TYPE_QOS_NULL, + .rate = 0xFF, + }; + + frame.skb = ieee80211_qosnullfunc_get(priv->hw, priv->vif); + if (!frame.skb) + return -ENOMEM; + + ret = wsm_set_template_frame(priv, &frame); + + dev_kfree_skb(frame.skb); + + */ + return 0; +} + +static int cw1200_enable_beaconing(struct cw1200_common *priv, + bool enable) +{ + struct wsm_beacon_transmit transmit = { + .enable_beaconing = enable, + }; + + return wsm_beacon_transmit(priv, &transmit); +} + +static int cw1200_start_ap(struct cw1200_common *priv) +{ + int ret; + struct ieee80211_bss_conf *conf = &priv->vif->bss_conf; + struct wsm_start start = { + .mode = priv->vif->p2p ? + WSM_START_MODE_P2P_GO : WSM_START_MODE_AP, + .band = (priv->channel->band == IEEE80211_BAND_5GHZ) ? + WSM_PHY_BAND_5G : WSM_PHY_BAND_2_4G, + .channel_number = priv->channel->hw_value, + .beacon_interval = conf->beacon_int, + .dtim_period = conf->dtim_period, + .preamble = conf->use_short_preamble ? + WSM_JOIN_PREAMBLE_SHORT : + WSM_JOIN_PREAMBLE_LONG, + .probe_delay = 100, + .basic_rate_set = cw1200_rate_mask_to_wsm(priv, + conf->basic_rates), + }; + struct wsm_operational_mode mode = { + .power_mode = cw1200_power_mode, + .disable_more_flag_usage = true, + }; + + memset(start.ssid, 0, sizeof(start.ssid)); + if (!conf->hidden_ssid) { + start.ssid_len = conf->ssid_len; + memcpy(start.ssid, conf->ssid, start.ssid_len); + } + + priv->beacon_int = conf->beacon_int; + priv->join_dtim_period = conf->dtim_period; + + memset(&priv->link_id_db, 0, sizeof(priv->link_id_db)); + + pr_debug("[AP] ch: %d(%d), bcn: %d(%d), brt: 0x%.8X, ssid: %.*s.\n", + start.channel_number, start.band, + start.beacon_interval, start.dtim_period, + start.basic_rate_set, + start.ssid_len, start.ssid); + ret = wsm_start(priv, &start); + if (!ret) + ret = cw1200_upload_keys(priv); + if (!ret && priv->vif->p2p) { + pr_debug("[AP] Setting p2p powersave configuration.\n"); + wsm_set_p2p_ps_modeinfo(priv, &priv->p2p_ps_modeinfo); + } + if (!ret) { + wsm_set_block_ack_policy(priv, 0, 0); + priv->join_status = CW1200_JOIN_STATUS_AP; + cw1200_update_filtering(priv); + } + wsm_set_operational_mode(priv, &mode); + return ret; +} + +static int cw1200_update_beaconing(struct cw1200_common *priv) +{ + struct ieee80211_bss_conf *conf = &priv->vif->bss_conf; + struct wsm_reset reset = { + .link_id = 0, + .reset_statistics = true, + }; + + if (priv->mode == NL80211_IFTYPE_AP) { + /* TODO: check if changed channel, band */ + if (priv->join_status != CW1200_JOIN_STATUS_AP || + priv->beacon_int != conf->beacon_int) { + pr_debug("ap restarting\n"); + wsm_lock_tx(priv); + if (priv->join_status != CW1200_JOIN_STATUS_PASSIVE) + wsm_reset(priv, &reset); + priv->join_status = CW1200_JOIN_STATUS_PASSIVE; + cw1200_start_ap(priv); + wsm_unlock_tx(priv); + } else + pr_debug("ap started join_status: %d\n", + priv->join_status); + } + return 0; +} diff --git a/drivers/net/wireless/acc1340/sta.h b/drivers/net/wireless/acc1340/sta.h new file mode 100644 index 0000000000000..b7e386b7662b6 --- /dev/null +++ b/drivers/net/wireless/acc1340/sta.h @@ -0,0 +1,124 @@ +/* + * Mac80211 STA interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 STA_H_INCLUDED +#define STA_H_INCLUDED + +/* ******************************************************************** */ +/* mac80211 API */ + +int cw1200_start(struct ieee80211_hw *dev); +void cw1200_stop(struct ieee80211_hw *dev); +int cw1200_add_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif); +void cw1200_remove_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif); +int cw1200_change_interface(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + enum nl80211_iftype new_type, + bool p2p); +int cw1200_config(struct ieee80211_hw *dev, u32 changed); +void cw1200_configure_filter(struct ieee80211_hw *dev, + unsigned int changed_flags, + unsigned int *total_flags, + u64 multicast); +int cw1200_conf_tx(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params); +int cw1200_get_stats(struct ieee80211_hw *dev, + struct ieee80211_low_level_stats *stats); +int cw1200_set_key(struct ieee80211_hw *dev, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key); + +int cw1200_set_rts_threshold(struct ieee80211_hw *hw, u32 value); + +void cw1200_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u32 queues, bool drop); + +u64 cw1200_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list); + +int cw1200_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg); + +/* ******************************************************************** */ +/* WSM callbacks */ + +void cw1200_join_complete_cb(struct cw1200_common *priv, + struct wsm_join_complete *arg); + +/* ******************************************************************** */ +/* WSM events */ + +void cw1200_free_event_queue(struct cw1200_common *priv); +void cw1200_event_handler(struct work_struct *work); +void cw1200_bss_loss_work(struct work_struct *work); +void cw1200_bss_params_work(struct work_struct *work); +void cw1200_keep_alive_work(struct work_struct *work); +void cw1200_tx_failure_work(struct work_struct *work); + +void __cw1200_cqm_bssloss_sm(struct cw1200_common *priv, int init, int good, + int bad); +static inline void cw1200_cqm_bssloss_sm(struct cw1200_common *priv, + int init, int good, int bad) +{ + spin_lock(&priv->bss_loss_lock); + __cw1200_cqm_bssloss_sm(priv, init, good, bad); + spin_unlock(&priv->bss_loss_lock); +} + +/* ******************************************************************** */ +/* Internal API */ + +int cw1200_setup_mac(struct cw1200_common *priv); +void cw1200_join_timeout(struct work_struct *work); +void cw1200_unjoin_work(struct work_struct *work); +void cw1200_join_complete_work(struct work_struct *work); +void cw1200_wep_key_work(struct work_struct *work); +void cw1200_update_listening(struct cw1200_common *priv, bool enabled); +void cw1200_update_filtering(struct cw1200_common *priv); +void cw1200_update_filtering_work(struct work_struct *work); +void cw1200_set_beacon_wakeup_period_work(struct work_struct *work); +int cw1200_enable_listening(struct cw1200_common *priv); +int cw1200_disable_listening(struct cw1200_common *priv); +int cw1200_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg); +void cw1200_ba_work(struct work_struct *work); +void cw1200_ba_timer(unsigned long arg); + +/* AP stuffs */ +int cw1200_set_tim(struct ieee80211_hw *dev, struct ieee80211_sta *sta, + bool set); +int cw1200_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +int cw1200_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta); +void cw1200_sta_notify(struct ieee80211_hw *dev, struct ieee80211_vif *vif, + enum sta_notify_cmd notify_cmd, + struct ieee80211_sta *sta); +void cw1200_bss_info_changed(struct ieee80211_hw *dev, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, + u32 changed); +int cw1200_ampdu_action(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, + u8 buf_size); + +void cw1200_suspend_resume(struct cw1200_common *priv, + struct wsm_suspend_resume *arg); +void cw1200_set_tim_work(struct work_struct *work); +void cw1200_set_cts_work(struct work_struct *work); +void cw1200_multicast_start_work(struct work_struct *work); +void cw1200_multicast_stop_work(struct work_struct *work); +void cw1200_mcast_timeout(unsigned long arg); + +#endif diff --git a/drivers/net/wireless/acc1340/txrx.c b/drivers/net/wireless/acc1340/txrx.c new file mode 100644 index 0000000000000..00d8934d2692b --- /dev/null +++ b/drivers/net/wireless/acc1340/txrx.c @@ -0,0 +1,1472 @@ +/* + * Datapath implementation for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "wsm.h" +#include "bh.h" +#include "sta.h" +#include "debug.h" + +#define CW1200_INVALID_RATE_ID (0xFF) + +static int cw1200_handle_action_rx(struct cw1200_common *priv, + struct sk_buff *skb); +static const struct ieee80211_rate * +cw1200_get_tx_rate(const struct cw1200_common *priv, + const struct ieee80211_tx_rate *rate); + +/* ******************************************************************** */ +/* TX queue lock / unlock */ + +static inline void cw1200_tx_queues_lock(struct cw1200_common *priv) +{ + int i; + for (i = 0; i < 4; ++i) + cw1200_queue_lock(&priv->tx_queue[i]); +} + +static inline void cw1200_tx_queues_unlock(struct cw1200_common *priv) +{ + int i; + for (i = 0; i < 4; ++i) + cw1200_queue_unlock(&priv->tx_queue[i]); +} + +/* ******************************************************************** */ +/* TX policy cache implementation */ + +static void tx_policy_dump(struct tx_policy *policy) +{ + pr_debug("[TX policy] %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X %.1X%.1X%.1X%.1X%.1X%.1X%.1X%.1X: %d\n", + policy->raw[0] & 0x0F, policy->raw[0] >> 4, + policy->raw[1] & 0x0F, policy->raw[1] >> 4, + policy->raw[2] & 0x0F, policy->raw[2] >> 4, + policy->raw[3] & 0x0F, policy->raw[3] >> 4, + policy->raw[4] & 0x0F, policy->raw[4] >> 4, + policy->raw[5] & 0x0F, policy->raw[5] >> 4, + policy->raw[6] & 0x0F, policy->raw[6] >> 4, + policy->raw[7] & 0x0F, policy->raw[7] >> 4, + policy->raw[8] & 0x0F, policy->raw[8] >> 4, + policy->raw[9] & 0x0F, policy->raw[9] >> 4, + policy->raw[10] & 0x0F, policy->raw[10] >> 4, + policy->raw[11] & 0x0F, policy->raw[11] >> 4, + policy->defined); +} + +static void tx_policy_build(const struct cw1200_common *priv, + /* [out] */ struct tx_policy *policy, + struct ieee80211_tx_rate *rates, size_t count) +{ + int i, j; + unsigned limit = priv->short_frame_max_tx_count; + unsigned total = 0; + BUG_ON(rates[0].idx < 0); + memset(policy, 0, sizeof(*policy)); + + /* Sort rates in descending order. */ + for (i = 1; i < count; ++i) { + if (rates[i].idx < 0) { + count = i; + break; + } + if (rates[i].idx > rates[i - 1].idx) { + struct ieee80211_tx_rate tmp = rates[i - 1]; + rates[i - 1] = rates[i]; + rates[i] = tmp; + } + } + + /* Eliminate duplicates. */ + total = rates[0].count; + for (i = 0, j = 1; j < count; ++j) { + if (rates[j].idx == rates[i].idx) { + rates[i].count += rates[j].count; + } else if (rates[j].idx > rates[i].idx) { + break; + } else { + ++i; + if (i != j) + rates[i] = rates[j]; + } + total += rates[j].count; + } + count = i + 1; + + /* Re-fill policy trying to keep every requested rate and with + * respect to the global max tx retransmission count. + */ + if (limit < count) + limit = count; + if (total > limit) { + for (i = 0; i < count; ++i) { + int left = count - i - 1; + if (rates[i].count > limit - left) + rates[i].count = limit - left; + limit -= rates[i].count; + } + } + + /* HACK!!! Device has problems (at least) switching from + * 54Mbps CTS to 1Mbps. This switch takes enormous amount + * of time (100-200 ms), leading to valuable throughput drop. + * As a workaround, additional g-rates are injected to the + * policy. + */ + if (count == 2 && !(rates[0].flags & IEEE80211_TX_RC_MCS) && + rates[0].idx > 4 && rates[0].count > 2 && + rates[1].idx < 2) { + int mid_rate = (rates[0].idx + 4) >> 1; + + /* Decrease number of retries for the initial rate */ + rates[0].count -= 2; + + if (mid_rate != 4) { + /* Keep fallback rate at 1Mbps. */ + rates[3] = rates[1]; + + /* Inject 1 transmission on lowest g-rate */ + rates[2].idx = 4; + rates[2].count = 1; + rates[2].flags = rates[1].flags; + + /* Inject 1 transmission on mid-rate */ + rates[1].idx = mid_rate; + rates[1].count = 1; + + /* Fallback to 1 Mbps is a really bad thing, + * so let's try to increase probability of + * successful transmission on the lowest g rate + * even more + */ + if (rates[0].count >= 3) { + --rates[0].count; + ++rates[2].count; + } + + /* Adjust amount of rates defined */ + count += 2; + } else { + /* Keep fallback rate at 1Mbps. */ + rates[2] = rates[1]; + + /* Inject 2 transmissions on lowest g-rate */ + rates[1].idx = 4; + rates[1].count = 2; + + /* Adjust amount of rates defined */ + count += 1; + } + } + + policy->defined = cw1200_get_tx_rate(priv, &rates[0])->hw_value + 1; + + for (i = 0; i < count; ++i) { + register unsigned rateid, off, shift, retries; + + rateid = cw1200_get_tx_rate(priv, &rates[i])->hw_value; + off = rateid >> 3; /* eq. rateid / 8 */ + shift = (rateid & 0x07) << 2; /* eq. (rateid % 8) * 4 */ + + retries = rates[i].count; + if (retries > 0x0F) { + rates[i].count = 0x0f; + retries = 0x0F; + } + policy->tbl[off] |= __cpu_to_le32(retries << shift); + policy->retry_count += retries; + } + + pr_debug("[TX policy] Policy (%zu): %d:%d, %d:%d, %d:%d, %d:%d\n", + count, + rates[0].idx, rates[0].count, + rates[1].idx, rates[1].count, + rates[2].idx, rates[2].count, + rates[3].idx, rates[3].count); +} + +static inline bool tx_policy_is_equal(const struct tx_policy *wanted, + const struct tx_policy *cached) +{ + size_t count = wanted->defined >> 1; + if (wanted->defined > cached->defined) + return false; + if (count) { + if (memcmp(wanted->raw, cached->raw, count)) + return false; + } + if (wanted->defined & 1) { + if ((wanted->raw[count] & 0x0F) != (cached->raw[count] & 0x0F)) + return false; + } + return true; +} + +static int tx_policy_find(struct tx_policy_cache *cache, + const struct tx_policy *wanted) +{ + /* O(n) complexity. Not so good, but there's only 8 entries in + * the cache. + * Also lru helps to reduce search time. + */ + struct tx_policy_cache_entry *it; + /* First search for policy in "used" list */ + list_for_each_entry(it, &cache->used, link) { + if (tx_policy_is_equal(wanted, &it->policy)) + return it - cache->cache; + } + /* Then - in "free list" */ + list_for_each_entry(it, &cache->free, link) { + if (tx_policy_is_equal(wanted, &it->policy)) + return it - cache->cache; + } + return -1; +} + +static inline void tx_policy_use(struct tx_policy_cache *cache, + struct tx_policy_cache_entry *entry) +{ + ++entry->policy.usage_count; + list_move(&entry->link, &cache->used); +} + +static inline int tx_policy_release(struct tx_policy_cache *cache, + struct tx_policy_cache_entry *entry) +{ + int ret = --entry->policy.usage_count; + if (!ret) + list_move(&entry->link, &cache->free); + return ret; +} + +void tx_policy_clean(struct cw1200_common *priv) +{ + int idx, locked; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + struct tx_policy_cache_entry *entry; + + cw1200_tx_queues_lock(priv); + spin_lock_bh(&cache->lock); + locked = list_empty(&cache->free); + + for (idx = 0; idx < TX_POLICY_CACHE_SIZE; idx++) { + entry = &cache->cache[idx]; + /* Policy usage count should be 0 at this time as all queues + should be empty + */ + if (WARN_ON(entry->policy.usage_count)) { + entry->policy.usage_count = 0; + list_move(&entry->link, &cache->free); + } + memset(&entry->policy, 0, sizeof(entry->policy)); + } + if (locked) + cw1200_tx_queues_unlock(priv); + + cw1200_tx_queues_unlock(priv); + spin_unlock_bh(&cache->lock); +} + +/* ******************************************************************** */ +/* External TX policy cache API */ + +void tx_policy_init(struct cw1200_common *priv) +{ + struct tx_policy_cache *cache = &priv->tx_policy_cache; + int i; + + memset(cache, 0, sizeof(*cache)); + + spin_lock_init(&cache->lock); + INIT_LIST_HEAD(&cache->used); + INIT_LIST_HEAD(&cache->free); + + for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) + list_add(&cache->cache[i].link, &cache->free); +} + +static int tx_policy_get(struct cw1200_common *priv, + struct ieee80211_tx_rate *rates, + size_t count, bool *renew) +{ + int idx; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + struct tx_policy wanted; + + tx_policy_build(priv, &wanted, rates, count); + + spin_lock_bh(&cache->lock); + if (WARN_ON_ONCE(list_empty(&cache->free))) { + spin_unlock_bh(&cache->lock); + return CW1200_INVALID_RATE_ID; + } + idx = tx_policy_find(cache, &wanted); + if (idx >= 0) { + pr_debug("[TX policy] Used TX policy: %d\n", idx); + *renew = false; + } else { + struct tx_policy_cache_entry *entry; + *renew = true; + /* If policy is not found create a new one + * using the oldest entry in "free" list + */ + entry = list_entry(cache->free.prev, + struct tx_policy_cache_entry, link); + entry->policy = wanted; + idx = entry - cache->cache; + pr_debug("[TX policy] New TX policy: %d\n", idx); + tx_policy_dump(&entry->policy); + } + tx_policy_use(cache, &cache->cache[idx]); + if (list_empty(&cache->free)) { + /* Lock TX queues. */ + cw1200_tx_queues_lock(priv); + } + spin_unlock_bh(&cache->lock); + return idx; +} + +static void tx_policy_put(struct cw1200_common *priv, int idx) +{ + int usage, locked; + struct tx_policy_cache *cache = &priv->tx_policy_cache; + + spin_lock_bh(&cache->lock); + locked = list_empty(&cache->free); + usage = tx_policy_release(cache, &cache->cache[idx]); + if (locked && !usage) { + /* Unlock TX queues. */ + cw1200_tx_queues_unlock(priv); + } + spin_unlock_bh(&cache->lock); +} + +static int tx_policy_upload(struct cw1200_common *priv) +{ + struct tx_policy_cache *cache = &priv->tx_policy_cache; + int i; + struct wsm_set_tx_rate_retry_policy arg = { + .num = 0, + }; + spin_lock_bh(&cache->lock); + + /* Upload only modified entries. */ + for (i = 0; i < TX_POLICY_CACHE_SIZE; ++i) { + struct tx_policy *src = &cache->cache[i].policy; + if (src->retry_count && !src->uploaded) { + struct wsm_tx_rate_retry_policy *dst = + &arg.tbl[arg.num]; + dst->index = i; + dst->short_retries = priv->short_frame_max_tx_count; + dst->long_retries = priv->long_frame_max_tx_count; + + dst->flags = WSM_TX_RATE_POLICY_FLAG_TERMINATE_WHEN_FINISHED | + WSM_TX_RATE_POLICY_FLAG_COUNT_INITIAL_TRANSMIT; + memcpy(dst->rate_count_indices, src->tbl, + sizeof(dst->rate_count_indices)); + src->uploaded = 1; + ++arg.num; + } + } + spin_unlock_bh(&cache->lock); + cw1200_debug_tx_cache_miss(priv); + pr_debug("[TX policy] Upload %d policies\n", arg.num); + return wsm_set_tx_rate_retry_policy(priv, &arg); +} + +void tx_policy_upload_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, tx_policy_upload_work); + + pr_debug("[TX] TX policy upload.\n"); + tx_policy_upload(priv); + + wsm_unlock_tx(priv); + cw1200_tx_queues_unlock(priv); +} + +/* ******************************************************************** */ +/* cw1200 TX implementation */ + +struct cw1200_txinfo { + struct sk_buff *skb; + unsigned queue; + struct ieee80211_tx_info *tx_info; + const struct ieee80211_rate *rate; + struct ieee80211_hdr *hdr; + size_t hdrlen; + const u8 *da; + struct cw1200_sta_priv *sta_priv; + struct ieee80211_sta *sta; + struct cw1200_txpriv txpriv; +}; + +u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, u32 rates) +{ + u32 ret = 0; + int i; + for (i = 0; i < 32; ++i) { + if (rates & BIT(i)) + ret |= BIT(priv->rates[i].hw_value); + } + return ret; +} + +static const struct ieee80211_rate * +cw1200_get_tx_rate(const struct cw1200_common *priv, + const struct ieee80211_tx_rate *rate) +{ + if (rate->idx < 0) + return NULL; + if (rate->flags & IEEE80211_TX_RC_MCS) + return &priv->mcs_rates[rate->idx]; + return &priv->hw->wiphy->bands[priv->channel->band]-> + bitrates[rate->idx]; +} + +static int +cw1200_tx_h_calc_link_ids(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (t->sta && t->sta_priv->link_id) + t->txpriv.raw_link_id = + t->txpriv.link_id = + t->sta_priv->link_id; + else if (priv->mode != NL80211_IFTYPE_AP) + t->txpriv.raw_link_id = + t->txpriv.link_id = 0; + else if (is_multicast_ether_addr(t->da)) { + if (priv->enable_beacon) { + t->txpriv.raw_link_id = 0; + t->txpriv.link_id = CW1200_LINK_ID_AFTER_DTIM; + } else { + t->txpriv.raw_link_id = 0; + t->txpriv.link_id = 0; + } + } else { + t->txpriv.link_id = cw1200_find_link_id(priv, t->da); + if (!t->txpriv.link_id) + t->txpriv.link_id = cw1200_alloc_link_id(priv, t->da); + if (!t->txpriv.link_id) { + wiphy_err(priv->hw->wiphy, + "No more link IDs available.\n"); + return -ENOENT; + } + t->txpriv.raw_link_id = t->txpriv.link_id; + } + if (t->txpriv.raw_link_id) + priv->link_id_db[t->txpriv.raw_link_id - 1].timestamp = + jiffies; + if (t->sta && (t->sta->uapsd_queues & BIT(t->queue))) + t->txpriv.link_id = CW1200_LINK_ID_UAPSD; + return 0; +} + +static void +cw1200_tx_h_pm(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (ieee80211_is_auth(t->hdr->frame_control)) { + u32 mask = ~BIT(t->txpriv.raw_link_id); + spin_lock_bh(&priv->ps_state_lock); + priv->sta_asleep_mask &= mask; + priv->pspoll_mask &= mask; + spin_unlock_bh(&priv->ps_state_lock); + } +} + +static void +cw1200_tx_h_calc_tid(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (ieee80211_is_data_qos(t->hdr->frame_control)) { + u8 *qos = ieee80211_get_qos_ctl(t->hdr); + t->txpriv.tid = qos[0] & IEEE80211_QOS_CTL_TID_MASK; + } else if (ieee80211_is_data(t->hdr->frame_control)) { + t->txpriv.tid = 0; + } +} + +static int +cw1200_tx_h_crypt(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + if (!t->tx_info->control.hw_key || + !ieee80211_has_protected(t->hdr->frame_control)) + return 0; + + t->hdrlen += t->tx_info->control.hw_key->iv_len; + skb_put(t->skb, t->tx_info->control.hw_key->icv_len); + + if (t->tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) + skb_put(t->skb, 8); /* MIC space */ + + return 0; +} + +static int +cw1200_tx_h_align(struct cw1200_common *priv, + struct cw1200_txinfo *t, + u8 *flags) +{ + size_t offset = (size_t)t->skb->data & 3; + + if (!offset) + return 0; + + if (offset & 1) { + wiphy_err(priv->hw->wiphy, + "Bug: attempt to transmit a frame with wrong alignment: %zu\n", + offset); + return -EINVAL; + } + + if (skb_headroom(t->skb) < offset) { + wiphy_err(priv->hw->wiphy, + "Bug: no space allocated for DMA alignment. headroom: %d\n", + skb_headroom(t->skb)); + return -ENOMEM; + } + skb_push(t->skb, offset); + t->hdrlen += offset; + t->txpriv.offset += offset; + *flags |= WSM_TX_2BYTES_SHIFT; + cw1200_debug_tx_align(priv); + return 0; +} + +static int +cw1200_tx_h_action(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + struct ieee80211_mgmt *mgmt = + (struct ieee80211_mgmt *)t->hdr; + if (ieee80211_is_action(t->hdr->frame_control) && + mgmt->u.action.category == WLAN_CATEGORY_BACK) + return 1; + else + return 0; +} + +/* Add WSM header */ +static struct wsm_tx * +cw1200_tx_h_wsm(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + struct wsm_tx *wsm; + + if (skb_headroom(t->skb) < sizeof(struct wsm_tx)) { + wiphy_err(priv->hw->wiphy, + "Bug: no space allocated for WSM header. headroom: %d\n", + skb_headroom(t->skb)); + return NULL; + } + + wsm = (struct wsm_tx *)skb_push(t->skb, sizeof(struct wsm_tx)); + t->txpriv.offset += sizeof(struct wsm_tx); + memset(wsm, 0, sizeof(*wsm)); + wsm->hdr.len = __cpu_to_le16(t->skb->len); + wsm->hdr.id = __cpu_to_le16(0x0004); + wsm->queue_id = wsm_queue_id_to_wsm(t->queue); + return wsm; +} + +/* BT Coex specific handling */ +static void +cw1200_tx_h_bt(struct cw1200_common *priv, + struct cw1200_txinfo *t, + struct wsm_tx *wsm) +{ + u8 priority = 0; + + if (!priv->bt_present) + return; + + if (ieee80211_is_nullfunc(t->hdr->frame_control)) { + priority = WSM_EPTA_PRIORITY_MGT; + } else if (ieee80211_is_data(t->hdr->frame_control)) { + /* Skip LLC SNAP header (+6) */ + u8 *payload = &t->skb->data[t->hdrlen]; + __be16 *ethertype = (__be16 *)&payload[6]; + if (be16_to_cpu(*ethertype) == ETH_P_PAE) + priority = WSM_EPTA_PRIORITY_EAPOL; + } else if (ieee80211_is_assoc_req(t->hdr->frame_control) || + ieee80211_is_reassoc_req(t->hdr->frame_control)) { + struct ieee80211_mgmt *mgt_frame = + (struct ieee80211_mgmt *)t->hdr; + + if (le16_to_cpu(mgt_frame->u.assoc_req.listen_interval) < + priv->listen_interval) { + pr_debug("Modified Listen Interval to %d from %d\n", + priv->listen_interval, + mgt_frame->u.assoc_req.listen_interval); + /* Replace listen interval derieved from + * the one read from SDD + */ + mgt_frame->u.assoc_req.listen_interval = cpu_to_le16(priv->listen_interval); + } + } + + if (!priority) { + if (ieee80211_is_action(t->hdr->frame_control)) + priority = WSM_EPTA_PRIORITY_ACTION; + else if (ieee80211_is_mgmt(t->hdr->frame_control)) + priority = WSM_EPTA_PRIORITY_MGT; + else if ((wsm->queue_id == WSM_QUEUE_VOICE)) + priority = WSM_EPTA_PRIORITY_VOICE; + else if ((wsm->queue_id == WSM_QUEUE_VIDEO)) + priority = WSM_EPTA_PRIORITY_VIDEO; + else + priority = WSM_EPTA_PRIORITY_DATA; + } + + pr_debug("[TX] EPTA priority %d.\n", priority); + + wsm->flags |= priority << 1; +} + +static int +cw1200_tx_h_rate_policy(struct cw1200_common *priv, + struct cw1200_txinfo *t, + struct wsm_tx *wsm) +{ + bool tx_policy_renew = false; + + t->txpriv.rate_id = tx_policy_get(priv, + t->tx_info->control.rates, IEEE80211_TX_MAX_RATES, + &tx_policy_renew); + if (t->txpriv.rate_id == CW1200_INVALID_RATE_ID) + return -EFAULT; + + wsm->flags |= t->txpriv.rate_id << 4; + + t->rate = cw1200_get_tx_rate(priv, + &t->tx_info->control.rates[0]), + wsm->max_tx_rate = t->rate->hw_value; + if (t->rate->flags & IEEE80211_TX_RC_MCS) { + if (cw1200_ht_greenfield(&priv->ht_info)) + wsm->ht_tx_parameters |= + __cpu_to_le32(WSM_HT_TX_GREENFIELD); + else + wsm->ht_tx_parameters |= + __cpu_to_le32(WSM_HT_TX_MIXED); + } + + if (tx_policy_renew) { + pr_debug("[TX] TX policy renew.\n"); + /* It's not so optimal to stop TX queues every now and then. + * Better to reimplement task scheduling with + * a counter. TODO. + */ + wsm_lock_tx_async(priv); + cw1200_tx_queues_lock(priv); + if (queue_work(priv->workqueue, + &priv->tx_policy_upload_work) <= 0) { + cw1200_tx_queues_unlock(priv); + wsm_unlock_tx(priv); + } + } + return 0; +} + +static bool +cw1200_tx_h_pm_state(struct cw1200_common *priv, + struct cw1200_txinfo *t) +{ + int was_buffered = 1; + + if (t->txpriv.link_id == CW1200_LINK_ID_AFTER_DTIM && + !priv->buffered_multicasts) { + priv->buffered_multicasts = true; + if (priv->sta_asleep_mask) + queue_work(priv->workqueue, + &priv->multicast_start_work); + } + + if (t->txpriv.raw_link_id && t->txpriv.tid < CW1200_MAX_TID) + was_buffered = priv->link_id_db[t->txpriv.raw_link_id - 1].buffered[t->txpriv.tid]++; + + return !was_buffered; +} + +/* ******************************************************************** */ + +void cw1200_tx(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct cw1200_common *priv = dev->priv; + struct cw1200_txinfo t = { + .skb = skb, + .queue = skb_get_queue_mapping(skb), + .tx_info = IEEE80211_SKB_CB(skb), + .hdr = (struct ieee80211_hdr *)skb->data, + .txpriv.tid = CW1200_MAX_TID, + .txpriv.rate_id = CW1200_INVALID_RATE_ID, + }; + struct ieee80211_sta *sta; + struct wsm_tx *wsm; + bool tid_update = 0; + u8 flags = 0; + int ret; + + if (priv->bh_error) + goto drop; + + t.hdrlen = ieee80211_hdrlen(t.hdr->frame_control); + t.da = ieee80211_get_DA(t.hdr); + if (control) { + t.sta = control->sta; + t.sta_priv = (struct cw1200_sta_priv *)&t.sta->drv_priv; + } + + if (WARN_ON(t.queue >= 4)) + goto drop; + + ret = cw1200_tx_h_calc_link_ids(priv, &t); + if (ret) + goto drop; + + pr_debug("[TX] TX %d bytes (queue: %d, link_id: %d (%d)).\n", + skb->len, t.queue, t.txpriv.link_id, + t.txpriv.raw_link_id); + + cw1200_tx_h_pm(priv, &t); + cw1200_tx_h_calc_tid(priv, &t); + ret = cw1200_tx_h_crypt(priv, &t); + if (ret) + goto drop; + ret = cw1200_tx_h_align(priv, &t, &flags); + if (ret) + goto drop; + ret = cw1200_tx_h_action(priv, &t); + if (ret) + goto drop; + wsm = cw1200_tx_h_wsm(priv, &t); + if (!wsm) { + ret = -ENOMEM; + goto drop; + } + wsm->flags |= flags; + cw1200_tx_h_bt(priv, &t, wsm); + ret = cw1200_tx_h_rate_policy(priv, &t, wsm); + if (ret) + goto drop; + + rcu_read_lock(); + sta = rcu_dereference(t.sta); + + spin_lock_bh(&priv->ps_state_lock); + { + tid_update = cw1200_tx_h_pm_state(priv, &t); + BUG_ON(cw1200_queue_put(&priv->tx_queue[t.queue], + t.skb, &t.txpriv)); + } + spin_unlock_bh(&priv->ps_state_lock); + + if (tid_update && sta) + ieee80211_sta_set_buffered(sta, t.txpriv.tid, true); + + rcu_read_unlock(); + + cw1200_bh_wakeup(priv); + + return; + +drop: + cw1200_skb_dtor(priv, skb, &t.txpriv); + return; +} + +/* ******************************************************************** */ + +static int cw1200_handle_action_rx(struct cw1200_common *priv, + struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt = (void *)skb->data; + + /* Filter block ACK negotiation: fully controlled by firmware */ + if (mgmt->u.action.category == WLAN_CATEGORY_BACK) + return 1; + + return 0; +} + +static int cw1200_handle_pspoll(struct cw1200_common *priv, + struct sk_buff *skb) +{ + struct ieee80211_sta *sta; + struct ieee80211_pspoll *pspoll = (struct ieee80211_pspoll *)skb->data; + int link_id = 0; + u32 pspoll_mask = 0; + int drop = 1; + int i; + + if (priv->join_status != CW1200_JOIN_STATUS_AP) + goto done; + if (memcmp(priv->vif->addr, pspoll->bssid, ETH_ALEN)) + goto done; + + rcu_read_lock(); + sta = ieee80211_find_sta(priv->vif, pspoll->ta); + if (sta) { + struct cw1200_sta_priv *sta_priv; + sta_priv = (struct cw1200_sta_priv *)&sta->drv_priv; + link_id = sta_priv->link_id; + pspoll_mask = BIT(sta_priv->link_id); + } + rcu_read_unlock(); + if (!link_id) + goto done; + + priv->pspoll_mask |= pspoll_mask; + drop = 0; + + /* Do not report pspols if data for given link id is queued already. */ + for (i = 0; i < 4; ++i) { + if (cw1200_queue_get_num_queued(&priv->tx_queue[i], + pspoll_mask)) { + cw1200_bh_wakeup(priv); + drop = 1; + break; + } + } + pr_debug("[RX] PSPOLL: %s\n", drop ? "local" : "fwd"); +done: + return drop; +} + +/* ******************************************************************** */ + +void cw1200_tx_confirm_cb(struct cw1200_common *priv, + int link_id, + struct wsm_tx_confirm *arg) +{ + u8 queue_id = cw1200_queue_get_queue_id(arg->packet_id); + struct cw1200_queue *queue = &priv->tx_queue[queue_id]; + struct sk_buff *skb; + const struct cw1200_txpriv *txpriv; + + pr_debug("[TX] TX confirm: %d, %d.\n", + arg->status, arg->ack_failures); + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) { + /* STA is stopped. */ + return; + } + + if (WARN_ON(queue_id >= 4)) + return; + + if (arg->status) + pr_debug("TX failed: %d.\n", arg->status); + + if ((arg->status == WSM_REQUEUE) && + (arg->flags & WSM_TX_STATUS_REQUEUE)) { + /* "Requeue" means "implicit suspend" */ + struct wsm_suspend_resume suspend = { + .link_id = link_id, + .stop = 1, + .multicast = !link_id, + }; + cw1200_suspend_resume(priv, &suspend); + wiphy_warn(priv->hw->wiphy, "Requeue for link_id %d (try %d). STAs asleep: 0x%.8X\n", + link_id, + cw1200_queue_get_generation(arg->packet_id) + 1, + priv->sta_asleep_mask); + cw1200_queue_requeue(queue, arg->packet_id); + spin_lock_bh(&priv->ps_state_lock); + if (!link_id) { + priv->buffered_multicasts = true; + if (priv->sta_asleep_mask) { + queue_work(priv->workqueue, + &priv->multicast_start_work); + } + } + spin_unlock_bh(&priv->ps_state_lock); + } else if (!cw1200_queue_get_skb(queue, arg->packet_id, + &skb, &txpriv)) { + struct ieee80211_tx_info *tx = IEEE80211_SKB_CB(skb); + int tx_count = arg->ack_failures; + u8 ht_flags = 0; + int i; + + if (cw1200_ht_greenfield(&priv->ht_info)) + ht_flags |= IEEE80211_TX_RC_GREEN_FIELD; + + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_state && + arg->packet_id == priv->bss_loss_confirm_id) { + if (arg->status) { + /* Recovery failed */ + __cw1200_cqm_bssloss_sm(priv, 0, 0, 1); + } else { + /* Recovery succeeded */ + __cw1200_cqm_bssloss_sm(priv, 0, 1, 0); + } + } + spin_unlock(&priv->bss_loss_lock); + + if (!arg->status) { + tx->flags |= IEEE80211_TX_STAT_ACK; + ++tx_count; + cw1200_debug_txed(priv); + if (arg->flags & WSM_TX_STATUS_AGGREGATION) { + /* Do not report aggregation to mac80211: + * it confuses minstrel a lot. + */ + /* tx->flags |= IEEE80211_TX_STAT_AMPDU; */ + cw1200_debug_txed_agg(priv); + } + } else { + if (tx_count) + ++tx_count; + } + + for (i = 0; i < IEEE80211_TX_MAX_RATES; ++i) { + if (tx->status.rates[i].count >= tx_count) { + tx->status.rates[i].count = tx_count; + break; + } + tx_count -= tx->status.rates[i].count; + if (tx->status.rates[i].flags & IEEE80211_TX_RC_MCS) + tx->status.rates[i].flags |= ht_flags; + } + + for (++i; i < IEEE80211_TX_MAX_RATES; ++i) { + tx->status.rates[i].count = 0; + tx->status.rates[i].idx = -1; + } + + /* Pull off any crypto trailers that we added on */ + if (tx->control.hw_key) { + skb_trim(skb, skb->len - tx->control.hw_key->icv_len); + if (tx->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) + skb_trim(skb, skb->len - 8); /* MIC space */ + } + cw1200_queue_remove(queue, arg->packet_id); + } + /* XXX TODO: Only wake if there are pending transmits.. */ + cw1200_bh_wakeup(priv); +} + +static void cw1200_notify_buffered_tx(struct cw1200_common *priv, + struct sk_buff *skb, int link_id, int tid) +{ + struct ieee80211_sta *sta; + struct ieee80211_hdr *hdr; + u8 *buffered; + u8 still_buffered = 0; + + if (link_id && tid < CW1200_MAX_TID) { + buffered = priv->link_id_db + [link_id - 1].buffered; + + spin_lock_bh(&priv->ps_state_lock); + if (!WARN_ON(!buffered[tid])) + still_buffered = --buffered[tid]; + spin_unlock_bh(&priv->ps_state_lock); + + if (!still_buffered && tid < CW1200_MAX_TID) { + hdr = (struct ieee80211_hdr *)skb->data; + rcu_read_lock(); + sta = ieee80211_find_sta(priv->vif, hdr->addr1); + if (sta) + ieee80211_sta_set_buffered(sta, tid, false); + rcu_read_unlock(); + } + } +} + +void cw1200_skb_dtor(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv) +{ + skb_pull(skb, txpriv->offset); + if (txpriv->rate_id != CW1200_INVALID_RATE_ID) { + cw1200_notify_buffered_tx(priv, skb, + txpriv->raw_link_id, txpriv->tid); + tx_policy_put(priv, txpriv->rate_id); + } + ieee80211_tx_status(priv->hw, skb); +} + +void cw1200_rx_cb(struct cw1200_common *priv, + struct wsm_rx *arg, + int link_id, + struct sk_buff **skb_p) +{ + struct sk_buff *skb = *skb_p; + struct ieee80211_rx_status *hdr = IEEE80211_SKB_RXCB(skb); + struct ieee80211_hdr *frame = (struct ieee80211_hdr *)skb->data; + struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; + struct cw1200_link_entry *entry = NULL; + unsigned long grace_period; + + bool early_data = false; + bool p2p = priv->vif && priv->vif->p2p; + size_t hdrlen; + hdr->flag = 0; + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) { + /* STA is stopped. */ + goto drop; + } + + if (link_id && link_id <= CW1200_MAX_STA_IN_AP_MODE) { + entry = &priv->link_id_db[link_id - 1]; + if (entry->status == CW1200_LINK_SOFT && + ieee80211_is_data(frame->frame_control)) + early_data = true; + entry->timestamp = jiffies; + } else if (p2p && + ieee80211_is_action(frame->frame_control) && + (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) { + pr_debug("[RX] Going to MAP&RESET link ID\n"); + WARN_ON(work_pending(&priv->linkid_reset_work)); + memcpy(&priv->action_frame_sa[0], + ieee80211_get_SA(frame), ETH_ALEN); + priv->action_linkid = 0; + schedule_work(&priv->linkid_reset_work); + } + + if (link_id && p2p && + ieee80211_is_action(frame->frame_control) && + (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC)) { + /* Link ID already exists for the ACTION frame. + * Reset and Remap + */ + WARN_ON(work_pending(&priv->linkid_reset_work)); + memcpy(&priv->action_frame_sa[0], + ieee80211_get_SA(frame), ETH_ALEN); + priv->action_linkid = link_id; + schedule_work(&priv->linkid_reset_work); + } + if (arg->status) { + if (arg->status == WSM_STATUS_MICFAILURE) { + pr_debug("[RX] MIC failure.\n"); + hdr->flag |= RX_FLAG_MMIC_ERROR; + } else if (arg->status == WSM_STATUS_NO_KEY_FOUND) { + pr_debug("[RX] No key found.\n"); + goto drop; + } else { + pr_debug("[RX] Receive failure: %d.\n", + arg->status); + goto drop; + } + } + + if (skb->len < sizeof(struct ieee80211_pspoll)) { + wiphy_warn(priv->hw->wiphy, "Mailformed SDU rx'ed. Size is lesser than IEEE header.\n"); + goto drop; + } + + if (ieee80211_is_pspoll(frame->frame_control)) + if (cw1200_handle_pspoll(priv, skb)) + goto drop; + + hdr->band = ((arg->channel_number & 0xff00) || + (arg->channel_number > 14)) ? + IEEE80211_BAND_5GHZ : IEEE80211_BAND_2GHZ; + hdr->freq = ieee80211_channel_to_frequency( + arg->channel_number, + hdr->band); + + if (arg->rx_rate >= 14) { + hdr->flag |= RX_FLAG_HT; + hdr->rate_idx = arg->rx_rate - 14; + } else if (arg->rx_rate >= 4) { + hdr->rate_idx = arg->rx_rate - 2; + } else { + hdr->rate_idx = arg->rx_rate; + } + + hdr->signal = (s8)arg->rcpi_rssi; + hdr->antenna = 0; + + hdrlen = ieee80211_hdrlen(frame->frame_control); + + if (WSM_RX_STATUS_ENCRYPTION(arg->flags)) { + size_t iv_len = 0, icv_len = 0; + + hdr->flag |= RX_FLAG_DECRYPTED | RX_FLAG_IV_STRIPPED; + + /* Oops... There is no fast way to ask mac80211 about + * IV/ICV lengths. Even defineas are not exposed. + */ + switch (WSM_RX_STATUS_ENCRYPTION(arg->flags)) { + case WSM_RX_STATUS_WEP: + iv_len = 4 /* WEP_IV_LEN */; + icv_len = 4 /* WEP_ICV_LEN */; + break; + case WSM_RX_STATUS_TKIP: + iv_len = 8 /* TKIP_IV_LEN */; + icv_len = 4 /* TKIP_ICV_LEN */ + + 8 /*MICHAEL_MIC_LEN*/; + hdr->flag |= RX_FLAG_MMIC_STRIPPED; + break; + case WSM_RX_STATUS_AES: + iv_len = 8 /* CCMP_HDR_LEN */; + icv_len = 8 /* CCMP_MIC_LEN */; + break; + case WSM_RX_STATUS_WAPI: + iv_len = 18 /* WAPI_HDR_LEN */; + icv_len = 16 /* WAPI_MIC_LEN */; + break; + default: + pr_warn("Unknown encryption type %d\n", + WSM_RX_STATUS_ENCRYPTION(arg->flags)); + goto drop; + } + + /* Firmware strips ICV in case of MIC failure. */ + if (arg->status == WSM_STATUS_MICFAILURE) + icv_len = 0; + + if (skb->len < hdrlen + iv_len + icv_len) { + wiphy_warn(priv->hw->wiphy, "Malformed SDU rx'ed. Size is lesser than crypto headers.\n"); + goto drop; + } + + /* Remove IV, ICV and MIC */ + skb_trim(skb, skb->len - icv_len); + memmove(skb->data + iv_len, skb->data, hdrlen); + skb_pull(skb, iv_len); + } + + /* Remove TSF from the end of frame */ + if (arg->flags & WSM_RX_STATUS_TSF_INCLUDED) { + memcpy(&hdr->mactime, skb->data + skb->len - 8, 8); + hdr->mactime = le64_to_cpu(hdr->mactime); + if (skb->len >= 8) + skb_trim(skb, skb->len - 8); + } else { + hdr->mactime = 0; + } + + cw1200_debug_rxed(priv); + if (arg->flags & WSM_RX_STATUS_AGGREGATE) + cw1200_debug_rxed_agg(priv); + + if (ieee80211_is_action(frame->frame_control) && + (arg->flags & WSM_RX_STATUS_ADDRESS1)) { + if (cw1200_handle_action_rx(priv, skb)) + return; + } else if (ieee80211_is_beacon(frame->frame_control) && + !arg->status && priv->vif && + ether_addr_equal(ieee80211_get_SA(frame), priv->vif->bss_conf.bssid)) { + const u8 *tim_ie; + u8 *ies = ((struct ieee80211_mgmt *) + (skb->data))->u.beacon.variable; + size_t ies_len = skb->len - (ies - (u8 *)(skb->data)); + + tim_ie = cfg80211_find_ie(WLAN_EID_TIM, ies, ies_len); + if (tim_ie) { + struct ieee80211_tim_ie *tim = + (struct ieee80211_tim_ie *)&tim_ie[2]; + + if (priv->join_dtim_period != tim->dtim_period) { + priv->join_dtim_period = tim->dtim_period; + queue_work(priv->workqueue, + &priv->set_beacon_wakeup_period_work); + } + } + + /* Disable beacon filter once we're associated... */ + if (priv->disable_beacon_filter && + (priv->vif->bss_conf.assoc || + priv->vif->bss_conf.ibss_joined)) { + priv->disable_beacon_filter = false; + queue_work(priv->workqueue, + &priv->update_filtering_work); + } + } + + /* Stay awake after frame is received to give + * userspace chance to react and acquire appropriate + * wakelock. + */ + if (ieee80211_is_auth(frame->frame_control)) + grace_period = 5 * HZ; + else if (ieee80211_is_deauth(frame->frame_control)) + grace_period = 5 * HZ; + else + grace_period = 1 * HZ; + cw1200_pm_stay_awake(&priv->pm_state, grace_period); + + if (early_data) { + spin_lock_bh(&priv->ps_state_lock); + /* Double-check status with lock held */ + if (entry->status == CW1200_LINK_SOFT) + skb_queue_tail(&entry->rx_queue, skb); + else + ieee80211_rx_irqsafe(priv->hw, skb); + spin_unlock_bh(&priv->ps_state_lock); + } else { + ieee80211_rx_irqsafe(priv->hw, skb); + } + *skb_p = NULL; + + return; + +drop: + /* TODO: update failure counters */ + return; +} + +/* ******************************************************************** */ +/* Security */ + +int cw1200_alloc_key(struct cw1200_common *priv) +{ + int idx; + + idx = ffs(~priv->key_map) - 1; + if (idx < 0 || idx > WSM_KEY_MAX_INDEX) + return -1; + + priv->key_map |= BIT(idx); + priv->keys[idx].index = idx; + return idx; +} + +void cw1200_free_key(struct cw1200_common *priv, int idx) +{ + BUG_ON(!(priv->key_map & BIT(idx))); + memset(&priv->keys[idx], 0, sizeof(priv->keys[idx])); + priv->key_map &= ~BIT(idx); +} + +void cw1200_free_keys(struct cw1200_common *priv) +{ + memset(&priv->keys, 0, sizeof(priv->keys)); + priv->key_map = 0; +} + +int cw1200_upload_keys(struct cw1200_common *priv) +{ + int idx, ret = 0; + for (idx = 0; idx <= WSM_KEY_MAX_INDEX; ++idx) + if (priv->key_map & BIT(idx)) { + ret = wsm_add_key(priv, &priv->keys[idx]); + if (ret < 0) + break; + } + return ret; +} + +/* Workaround for WFD test case 6.1.10 */ +void cw1200_link_id_reset(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, linkid_reset_work); + int temp_linkid; + + if (!priv->action_linkid) { + /* In GO mode we can receive ACTION frames without a linkID */ + temp_linkid = cw1200_alloc_link_id(priv, + &priv->action_frame_sa[0]); + WARN_ON(!temp_linkid); + if (temp_linkid) { + /* Make sure we execute the WQ */ + flush_workqueue(priv->workqueue); + /* Release the link ID */ + spin_lock_bh(&priv->ps_state_lock); + priv->link_id_db[temp_linkid - 1].prev_status = + priv->link_id_db[temp_linkid - 1].status; + priv->link_id_db[temp_linkid - 1].status = + CW1200_LINK_RESET; + spin_unlock_bh(&priv->ps_state_lock); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + } + } else { + spin_lock_bh(&priv->ps_state_lock); + priv->link_id_db[priv->action_linkid - 1].prev_status = + priv->link_id_db[priv->action_linkid - 1].status; + priv->link_id_db[priv->action_linkid - 1].status = + CW1200_LINK_RESET_REMAP; + spin_unlock_bh(&priv->ps_state_lock); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + flush_workqueue(priv->workqueue); + } +} + +int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac) +{ + int i, ret = 0; + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (!memcmp(mac, priv->link_id_db[i].mac, ETH_ALEN) && + priv->link_id_db[i].status) { + priv->link_id_db[i].timestamp = jiffies; + ret = i + 1; + break; + } + } + spin_unlock_bh(&priv->ps_state_lock); + return ret; +} + +int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac) +{ + int i, ret = 0; + unsigned long max_inactivity = 0; + unsigned long now = jiffies; + + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + if (!priv->link_id_db[i].status) { + ret = i + 1; + break; + } else if (priv->link_id_db[i].status != CW1200_LINK_HARD && + !priv->tx_queue_stats.link_map_cache[i + 1]) { + unsigned long inactivity = + now - priv->link_id_db[i].timestamp; + if (inactivity < max_inactivity) + continue; + max_inactivity = inactivity; + ret = i + 1; + } + } + if (ret) { + struct cw1200_link_entry *entry = &priv->link_id_db[ret - 1]; + pr_debug("[AP] STA added, link_id: %d\n", ret); + entry->status = CW1200_LINK_RESERVE; + memcpy(&entry->mac, mac, ETH_ALEN); + memset(&entry->buffered, 0, CW1200_MAX_TID); + skb_queue_head_init(&entry->rx_queue); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, &priv->link_id_work) <= 0) + wsm_unlock_tx(priv); + } else { + wiphy_info(priv->hw->wiphy, + "[AP] Early: no more link IDs available.\n"); + } + + spin_unlock_bh(&priv->ps_state_lock); + return ret; +} + +void cw1200_link_id_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, link_id_work); + wsm_flush_tx(priv); + cw1200_link_id_gc_work(&priv->link_id_gc_work.work); + wsm_unlock_tx(priv); +} + +void cw1200_link_id_gc_work(struct work_struct *work) +{ + struct cw1200_common *priv = + container_of(work, struct cw1200_common, link_id_gc_work.work); + struct wsm_reset reset = { + .reset_statistics = false, + }; + struct wsm_map_link map_link = { + .link_id = 0, + }; + unsigned long now = jiffies; + unsigned long next_gc = -1; + long ttl; + bool need_reset; + u32 mask; + int i; + + if (priv->join_status != CW1200_JOIN_STATUS_AP) + return; + + wsm_lock_tx(priv); + spin_lock_bh(&priv->ps_state_lock); + for (i = 0; i < CW1200_MAX_STA_IN_AP_MODE; ++i) { + need_reset = false; + mask = BIT(i + 1); + if (priv->link_id_db[i].status == CW1200_LINK_RESERVE || + (priv->link_id_db[i].status == CW1200_LINK_HARD && + !(priv->link_id_map & mask))) { + if (priv->link_id_map & mask) { + priv->sta_asleep_mask &= ~mask; + priv->pspoll_mask &= ~mask; + need_reset = true; + } + priv->link_id_map |= mask; + if (priv->link_id_db[i].status != CW1200_LINK_HARD) + priv->link_id_db[i].status = CW1200_LINK_SOFT; + memcpy(map_link.mac_addr, priv->link_id_db[i].mac, + ETH_ALEN); + spin_unlock_bh(&priv->ps_state_lock); + if (need_reset) { + reset.link_id = i + 1; + wsm_reset(priv, &reset); + } + map_link.link_id = i + 1; + wsm_map_link(priv, &map_link); + next_gc = min(next_gc, CW1200_LINK_ID_GC_TIMEOUT); + spin_lock_bh(&priv->ps_state_lock); + } else if (priv->link_id_db[i].status == CW1200_LINK_SOFT) { + ttl = priv->link_id_db[i].timestamp - now + + CW1200_LINK_ID_GC_TIMEOUT; + if (ttl <= 0) { + need_reset = true; + priv->link_id_db[i].status = CW1200_LINK_OFF; + priv->link_id_map &= ~mask; + priv->sta_asleep_mask &= ~mask; + priv->pspoll_mask &= ~mask; + eth_zero_addr(map_link.mac_addr); + spin_unlock_bh(&priv->ps_state_lock); + reset.link_id = i + 1; + wsm_reset(priv, &reset); + spin_lock_bh(&priv->ps_state_lock); + } else { + next_gc = min_t(unsigned long, next_gc, ttl); + } + } else if (priv->link_id_db[i].status == CW1200_LINK_RESET || + priv->link_id_db[i].status == + CW1200_LINK_RESET_REMAP) { + int status = priv->link_id_db[i].status; + priv->link_id_db[i].status = + priv->link_id_db[i].prev_status; + priv->link_id_db[i].timestamp = now; + reset.link_id = i + 1; + spin_unlock_bh(&priv->ps_state_lock); + wsm_reset(priv, &reset); + if (status == CW1200_LINK_RESET_REMAP) { + memcpy(map_link.mac_addr, + priv->link_id_db[i].mac, + ETH_ALEN); + map_link.link_id = i + 1; + wsm_map_link(priv, &map_link); + next_gc = min(next_gc, + CW1200_LINK_ID_GC_TIMEOUT); + } + spin_lock_bh(&priv->ps_state_lock); + } + if (need_reset) { + skb_queue_purge(&priv->link_id_db[i].rx_queue); + pr_debug("[AP] STA removed, link_id: %d\n", + reset.link_id); + } + } + spin_unlock_bh(&priv->ps_state_lock); + if (next_gc != -1) + queue_delayed_work(priv->workqueue, + &priv->link_id_gc_work, next_gc); + wsm_unlock_tx(priv); +} diff --git a/drivers/net/wireless/acc1340/txrx.h b/drivers/net/wireless/acc1340/txrx.h new file mode 100644 index 0000000000000..492a4e14213bd --- /dev/null +++ b/drivers/net/wireless/acc1340/txrx.h @@ -0,0 +1,106 @@ +/* + * Datapath interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 CW1200_TXRX_H +#define CW1200_TXRX_H + +#include + +/* extern */ struct ieee80211_hw; +/* extern */ struct sk_buff; +/* extern */ struct wsm_tx; +/* extern */ struct wsm_rx; +/* extern */ struct wsm_tx_confirm; +/* extern */ struct cw1200_txpriv; + +struct tx_policy { + union { + __le32 tbl[3]; + u8 raw[12]; + }; + u8 defined; + u8 usage_count; + u8 retry_count; + u8 uploaded; +}; + +struct tx_policy_cache_entry { + struct tx_policy policy; + struct list_head link; +}; + +#define TX_POLICY_CACHE_SIZE (8) +struct tx_policy_cache { + struct tx_policy_cache_entry cache[TX_POLICY_CACHE_SIZE]; + struct list_head used; + struct list_head free; + spinlock_t lock; /* Protect policy cache */ +}; + +/* ******************************************************************** */ +/* TX policy cache */ +/* Intention of TX policy cache is an overcomplicated WSM API. + * Device does not accept per-PDU tx retry sequence. + * It uses "tx retry policy id" instead, so driver code has to sync + * linux tx retry sequences with a retry policy table in the device. + */ +void tx_policy_init(struct cw1200_common *priv); +void tx_policy_upload_work(struct work_struct *work); +void tx_policy_clean(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* TX implementation */ + +u32 cw1200_rate_mask_to_wsm(struct cw1200_common *priv, + u32 rates); +void cw1200_tx(struct ieee80211_hw *dev, + struct ieee80211_tx_control *control, + struct sk_buff *skb); +void cw1200_skb_dtor(struct cw1200_common *priv, + struct sk_buff *skb, + const struct cw1200_txpriv *txpriv); + +/* ******************************************************************** */ +/* WSM callbacks */ + +void cw1200_tx_confirm_cb(struct cw1200_common *priv, + int link_id, + struct wsm_tx_confirm *arg); +void cw1200_rx_cb(struct cw1200_common *priv, + struct wsm_rx *arg, + int link_id, + struct sk_buff **skb_p); + +/* ******************************************************************** */ +/* Timeout */ + +void cw1200_tx_timeout(struct work_struct *work); + +/* ******************************************************************** */ +/* Security */ +int cw1200_alloc_key(struct cw1200_common *priv); +void cw1200_free_key(struct cw1200_common *priv, int idx); +void cw1200_free_keys(struct cw1200_common *priv); +int cw1200_upload_keys(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* Workaround for WFD test case 6.1.10 */ +void cw1200_link_id_reset(struct work_struct *work); + +#define CW1200_LINK_ID_GC_TIMEOUT ((unsigned long)(10 * HZ)) + +int cw1200_find_link_id(struct cw1200_common *priv, const u8 *mac); +int cw1200_alloc_link_id(struct cw1200_common *priv, const u8 *mac); +void cw1200_link_id_work(struct work_struct *work); +void cw1200_link_id_gc_work(struct work_struct *work); + + +#endif /* CW1200_TXRX_H */ diff --git a/drivers/net/wireless/acc1340/wsm.c b/drivers/net/wireless/acc1340/wsm.c new file mode 100644 index 0000000000000..5ab09f4223004 --- /dev/null +++ b/drivers/net/wireless/acc1340/wsm.c @@ -0,0 +1,1822 @@ +/* + * WSM host interface (HI) implementation for + * ST-Ericsson CW1200 mac80211 drivers. + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * 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 "acc1340.h" +#include "wsm.h" +#include "bh.h" +#include "sta.h" +#include "debug.h" + +#define WSM_CMD_TIMEOUT (2 * HZ) /* With respect to interrupt loss */ +#define WSM_CMD_START_TIMEOUT (7 * HZ) +#define WSM_CMD_RESET_TIMEOUT (3 * HZ) /* 2 sec. timeout was observed. */ +#define WSM_CMD_MAX_TIMEOUT (3 * HZ) + +#define WSM_SKIP(buf, size) \ + do { \ + if ((buf)->data + size > (buf)->end) \ + goto underflow; \ + (buf)->data += size; \ + } while (0) + +#define WSM_GET(buf, ptr, size) \ + do { \ + if ((buf)->data + size > (buf)->end) \ + goto underflow; \ + memcpy(ptr, (buf)->data, size); \ + (buf)->data += size; \ + } while (0) + +#define __WSM_GET(buf, type, type2, cvt) \ + ({ \ + type val; \ + if ((buf)->data + sizeof(type) > (buf)->end) \ + goto underflow; \ + val = cvt(*(type2 *)(buf)->data); \ + (buf)->data += sizeof(type); \ + val; \ + }) + +#define WSM_GET8(buf) __WSM_GET(buf, u8, u8, (u8)) +#define WSM_GET16(buf) __WSM_GET(buf, u16, __le16, __le16_to_cpu) +#define WSM_GET32(buf) __WSM_GET(buf, u32, __le32, __le32_to_cpu) + +#define WSM_PUT(buf, ptr, size) \ + do { \ + if ((buf)->data + size > (buf)->end) \ + if (wsm_buf_reserve((buf), size)) \ + goto nomem; \ + memcpy((buf)->data, ptr, size); \ + (buf)->data += size; \ + } while (0) + +#define __WSM_PUT(buf, val, type, type2, cvt) \ + do { \ + if ((buf)->data + sizeof(type) > (buf)->end) \ + if (wsm_buf_reserve((buf), sizeof(type))) \ + goto nomem; \ + *(type2 *)(buf)->data = cvt(val); \ + (buf)->data += sizeof(type); \ + } while (0) + +#define WSM_PUT8(buf, val) __WSM_PUT(buf, val, u8, u8, (u8)) +#define WSM_PUT16(buf, val) __WSM_PUT(buf, val, u16, __le16, __cpu_to_le16) +#define WSM_PUT32(buf, val) __WSM_PUT(buf, val, u32, __le32, __cpu_to_le32) + +static void wsm_buf_reset(struct wsm_buf *buf); +static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size); + +static int wsm_cmd_send(struct cw1200_common *priv, + struct wsm_buf *buf, + void *arg, u16 cmd, long tmo); + +#define wsm_cmd_lock(__priv) mutex_lock(&((__priv)->wsm_cmd_mux)) +#define wsm_cmd_unlock(__priv) mutex_unlock(&((__priv)->wsm_cmd_mux)) + +/* ******************************************************************** */ +/* WSM API implementation */ + +static int wsm_generic_confirm(struct cw1200_common *priv, + void *arg, + struct wsm_buf *buf) +{ + u32 status = WSM_GET32(buf); + if (status != WSM_STATUS_SUCCESS) + return -EINVAL; + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +int wsm_configuration(struct cw1200_common *priv, struct wsm_configuration *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->dot11MaxTransmitMsduLifeTime); + WSM_PUT32(buf, arg->dot11MaxReceiveLifeTime); + WSM_PUT32(buf, arg->dot11RtsThreshold); + + /* DPD block. */ + WSM_PUT16(buf, arg->dpdData_size + 12); + WSM_PUT16(buf, 1); /* DPD version */ + WSM_PUT(buf, arg->dot11StationId, ETH_ALEN); + WSM_PUT16(buf, 5); /* DPD flags */ + WSM_PUT(buf, arg->dpdData, arg->dpdData_size); + + ret = wsm_cmd_send(priv, buf, arg, + WSM_CONFIGURATION_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_configuration_confirm(struct cw1200_common *priv, + struct wsm_configuration *arg, + struct wsm_buf *buf) +{ + int i; + int status; + + status = WSM_GET32(buf); + if (WARN_ON(status != WSM_STATUS_SUCCESS)) + return -EINVAL; + + WSM_GET(buf, arg->dot11StationId, ETH_ALEN); + arg->dot11FrequencyBandsSupported = WSM_GET8(buf); + WSM_SKIP(buf, 1); + arg->supportedRateMask = WSM_GET32(buf); + for (i = 0; i < 2; ++i) { + arg->txPowerRange[i].min_power_level = WSM_GET32(buf); + arg->txPowerRange[i].max_power_level = WSM_GET32(buf); + arg->txPowerRange[i].stepping = WSM_GET32(buf); + } + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u16 cmd = WSM_RESET_REQ_ID | WSM_TX_LINK_ID(arg->link_id); + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->reset_statistics ? 0 : 1); + ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_RESET_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +struct wsm_mib { + u16 mib_id; + void *buf; + size_t buf_size; +}; + +int wsm_read_mib(struct cw1200_common *priv, u16 mib_id, void *_buf, + size_t buf_size) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + struct wsm_mib mib_buf = { + .mib_id = mib_id, + .buf = _buf, + .buf_size = buf_size, + }; + wsm_cmd_lock(priv); + + WSM_PUT16(buf, mib_id); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, &mib_buf, + WSM_READ_MIB_REQ_ID, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_read_mib_confirm(struct cw1200_common *priv, + struct wsm_mib *arg, + struct wsm_buf *buf) +{ + u16 size; + if (WARN_ON(WSM_GET32(buf) != WSM_STATUS_SUCCESS)) + return -EINVAL; + + if (WARN_ON(WSM_GET16(buf) != arg->mib_id)) + return -EINVAL; + + size = WSM_GET16(buf); + if (size > arg->buf_size) + size = arg->buf_size; + + WSM_GET(buf, arg->buf, size); + arg->buf_size = size; + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +int wsm_write_mib(struct cw1200_common *priv, u16 mib_id, void *_buf, + size_t buf_size) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + struct wsm_mib mib_buf = { + .mib_id = mib_id, + .buf = _buf, + .buf_size = buf_size, + }; + + wsm_cmd_lock(priv); + + WSM_PUT16(buf, mib_id); + WSM_PUT16(buf, buf_size); + WSM_PUT(buf, _buf, buf_size); + + ret = wsm_cmd_send(priv, buf, &mib_buf, + WSM_WRITE_MIB_REQ_ID, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +static int wsm_write_mib_confirm(struct cw1200_common *priv, + struct wsm_mib *arg, + struct wsm_buf *buf) +{ + int ret; + + ret = wsm_generic_confirm(priv, arg, buf); + if (ret) + return ret; + + if (arg->mib_id == WSM_MIB_ID_OPERATIONAL_POWER_MODE) { + /* OperationalMode: update PM status. */ + const char *p = arg->buf; + cw1200_enable_powersave(priv, (p[0] & 0x0F) ? true : false); + } + return 0; +} + +/* ******************************************************************** */ + +int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg) +{ + int i; + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + if (arg->num_channels > 48) + return -EINVAL; + + if (arg->num_ssids > 2) + return -EINVAL; + + if (arg->band > 1) + return -EINVAL; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->band); + WSM_PUT8(buf, arg->type); + WSM_PUT8(buf, arg->flags); + WSM_PUT8(buf, arg->max_tx_rate); + WSM_PUT32(buf, arg->auto_scan_interval); + WSM_PUT8(buf, arg->num_probes); + WSM_PUT8(buf, arg->num_channels); + WSM_PUT8(buf, arg->num_ssids); + WSM_PUT8(buf, arg->probe_delay); + + for (i = 0; i < arg->num_channels; ++i) { + WSM_PUT16(buf, arg->ch[i].number); + WSM_PUT16(buf, 0); + WSM_PUT32(buf, arg->ch[i].min_chan_time); + WSM_PUT32(buf, arg->ch[i].max_chan_time); + WSM_PUT32(buf, 0); + } + + for (i = 0; i < arg->num_ssids; ++i) { + WSM_PUT32(buf, arg->ssids[i].length); + WSM_PUT(buf, &arg->ssids[i].ssid[0], + sizeof(arg->ssids[i].ssid)); + } + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_START_SCAN_REQ_ID, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_stop_scan(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, + WSM_STOP_SCAN_REQ_ID, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + + +static int wsm_tx_confirm(struct cw1200_common *priv, + struct wsm_buf *buf, + int link_id) +{ + struct wsm_tx_confirm tx_confirm; + + tx_confirm.packet_id = WSM_GET32(buf); + tx_confirm.status = WSM_GET32(buf); + tx_confirm.tx_rate = WSM_GET8(buf); + tx_confirm.ack_failures = WSM_GET8(buf); + tx_confirm.flags = WSM_GET16(buf); + tx_confirm.media_delay = WSM_GET32(buf); + tx_confirm.tx_queue_delay = WSM_GET32(buf); + + cw1200_tx_confirm_cb(priv, link_id, &tx_confirm); + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +static int wsm_multi_tx_confirm(struct cw1200_common *priv, + struct wsm_buf *buf, int link_id) +{ + int ret; + int count; + int i; + + count = WSM_GET32(buf); + if (WARN_ON(count <= 0)) + return -EINVAL; + + if (count > 1) { + /* We already released one buffer, now for the rest */ + ret = wsm_release_tx_buffer(priv, count - 1); + if (ret < 0) + return ret; + else if (ret > 0) + cw1200_bh_wakeup(priv); + } + + cw1200_debug_txed_multi(priv, count); + for (i = 0; i < count; ++i) { + ret = wsm_tx_confirm(priv, buf, link_id); + if (ret) + return ret; + } + return ret; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +/* ******************************************************************** */ + +static int wsm_join_confirm(struct cw1200_common *priv, + struct wsm_join_cnf *arg, + struct wsm_buf *buf) +{ + arg->status = WSM_GET32(buf); + if (WARN_ON(arg->status) != WSM_STATUS_SUCCESS) + return -EINVAL; + + arg->min_power_level = WSM_GET32(buf); + arg->max_power_level = WSM_GET32(buf); + + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +int wsm_join(struct cw1200_common *priv, struct wsm_join *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + struct wsm_join_cnf resp; + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->band); + WSM_PUT16(buf, arg->channel_number); + WSM_PUT(buf, &arg->bssid[0], sizeof(arg->bssid)); + WSM_PUT16(buf, arg->atim_window); + WSM_PUT8(buf, arg->preamble_type); + WSM_PUT8(buf, arg->probe_for_join); + WSM_PUT8(buf, arg->dtim_period); + WSM_PUT8(buf, arg->flags); + WSM_PUT32(buf, arg->ssid_len); + WSM_PUT(buf, &arg->ssid[0], sizeof(arg->ssid)); + WSM_PUT32(buf, arg->beacon_interval); + WSM_PUT32(buf, arg->basic_rate_set); + + priv->tx_burst_idx = -1; + ret = wsm_cmd_send(priv, buf, &resp, + WSM_JOIN_REQ_ID, WSM_CMD_TIMEOUT); + /* TODO: Update state based on resp.min|max_power_level */ + + priv->join_complete_status = resp.status; + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_bss_params(struct cw1200_common *priv, + const struct wsm_set_bss_params *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, (arg->reset_beacon_loss ? 0x1 : 0)); + WSM_PUT8(buf, arg->beacon_lost_count); + WSM_PUT16(buf, arg->aid); + WSM_PUT32(buf, arg->operational_rate_set); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_SET_BSS_PARAMS_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT(buf, arg, sizeof(*arg)); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_ADD_KEY_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_remove_key(struct cw1200_common *priv, const struct wsm_remove_key *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->index); + WSM_PUT8(buf, 0); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_REMOVE_KEY_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_tx_queue_params(struct cw1200_common *priv, + const struct wsm_set_tx_queue_params *arg, u8 id) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u8 queue_id_to_wmm_aci[] = {3, 2, 0, 1}; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, queue_id_to_wmm_aci[id]); + WSM_PUT8(buf, 0); + WSM_PUT8(buf, arg->ackPolicy); + WSM_PUT8(buf, 0); + WSM_PUT32(buf, arg->maxTransmitLifetime); + WSM_PUT16(buf, arg->allowedMediumTime); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, 0x0012, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_edca_params(struct cw1200_common *priv, + const struct wsm_edca_params *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + /* Implemented according to specification. */ + + WSM_PUT16(buf, arg->params[3].cwmin); + WSM_PUT16(buf, arg->params[2].cwmin); + WSM_PUT16(buf, arg->params[1].cwmin); + WSM_PUT16(buf, arg->params[0].cwmin); + + WSM_PUT16(buf, arg->params[3].cwmax); + WSM_PUT16(buf, arg->params[2].cwmax); + WSM_PUT16(buf, arg->params[1].cwmax); + WSM_PUT16(buf, arg->params[0].cwmax); + + WSM_PUT8(buf, arg->params[3].aifns); + WSM_PUT8(buf, arg->params[2].aifns); + WSM_PUT8(buf, arg->params[1].aifns); + WSM_PUT8(buf, arg->params[0].aifns); + + WSM_PUT16(buf, arg->params[3].txop_limit); + WSM_PUT16(buf, arg->params[2].txop_limit); + WSM_PUT16(buf, arg->params[1].txop_limit); + WSM_PUT16(buf, arg->params[0].txop_limit); + + WSM_PUT32(buf, arg->params[3].max_rx_lifetime); + WSM_PUT32(buf, arg->params[2].max_rx_lifetime); + WSM_PUT32(buf, arg->params[1].max_rx_lifetime); + WSM_PUT32(buf, arg->params[0].max_rx_lifetime); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_EDCA_PARAMS_REQ_ID, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_switch_channel(struct cw1200_common *priv, + const struct wsm_switch_channel *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->switch_count); + WSM_PUT16(buf, arg->channel_number); + + priv->channel_switch_in_progress = 1; + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_SWITCH_CHANNEL_REQ_ID, WSM_CMD_TIMEOUT); + if (ret) + priv->channel_switch_in_progress = 0; + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + priv->ps_mode_switch_in_progress = 1; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->fast_psm_idle_period); + WSM_PUT8(buf, arg->ap_psm_change_period); + WSM_PUT8(buf, arg->min_auto_pspoll_period); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_SET_PM_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT8(buf, arg->mode); + WSM_PUT8(buf, arg->band); + WSM_PUT16(buf, arg->channel_number); + WSM_PUT32(buf, arg->ct_window); + WSM_PUT32(buf, arg->beacon_interval); + WSM_PUT8(buf, arg->dtim_period); + WSM_PUT8(buf, arg->preamble); + WSM_PUT8(buf, arg->probe_delay); + WSM_PUT8(buf, arg->ssid_len); + WSM_PUT(buf, arg->ssid, sizeof(arg->ssid)); + WSM_PUT32(buf, arg->basic_rate_set); + + priv->tx_burst_idx = -1; + ret = wsm_cmd_send(priv, buf, NULL, + WSM_START_REQ_ID, WSM_CMD_START_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_beacon_transmit(struct cw1200_common *priv, + const struct wsm_beacon_transmit *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT32(buf, arg->enable_beaconing ? 1 : 0); + + ret = wsm_cmd_send(priv, buf, NULL, + WSM_BEACON_TRANSMIT_REQ_ID, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_start_find(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, 0x0019, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + +/* ******************************************************************** */ + +int wsm_stop_find(struct cw1200_common *priv) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + ret = wsm_cmd_send(priv, buf, NULL, 0x001A, WSM_CMD_TIMEOUT); + wsm_cmd_unlock(priv); + return ret; +} + +/* ******************************************************************** */ + +int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + u16 cmd = 0x001C | WSM_TX_LINK_ID(arg->link_id); + + wsm_cmd_lock(priv); + + WSM_PUT(buf, &arg->mac_addr[0], sizeof(arg->mac_addr)); + WSM_PUT16(buf, 0); + + ret = wsm_cmd_send(priv, buf, NULL, cmd, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ + +int wsm_update_ie(struct cw1200_common *priv, + const struct wsm_update_ie *arg) +{ + int ret; + struct wsm_buf *buf = &priv->wsm_cmd_buf; + + wsm_cmd_lock(priv); + + WSM_PUT16(buf, arg->what); + WSM_PUT16(buf, arg->count); + WSM_PUT(buf, arg->ies, arg->length); + + ret = wsm_cmd_send(priv, buf, NULL, 0x001B, WSM_CMD_TIMEOUT); + + wsm_cmd_unlock(priv); + return ret; + +nomem: + wsm_cmd_unlock(priv); + return -ENOMEM; +} + +/* ******************************************************************** */ +int wsm_set_probe_responder(struct cw1200_common *priv, bool enable) +{ + priv->rx_filter.probeResponder = enable; + return wsm_set_rx_filter(priv, &priv->rx_filter); +} + +/* ******************************************************************** */ +/* WSM indication events implementation */ +const char * const cw1200_fw_types[] = { + "ETF", + "WFM", + "WSM", + "HI test", + "Platform test" +}; + +static int wsm_startup_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + priv->wsm_caps.input_buffers = WSM_GET16(buf); + priv->wsm_caps.input_buffer_size = WSM_GET16(buf); + priv->wsm_caps.hw_id = WSM_GET16(buf); + priv->wsm_caps.hw_subid = WSM_GET16(buf); + priv->wsm_caps.status = WSM_GET16(buf); + priv->wsm_caps.fw_cap = WSM_GET16(buf); + priv->wsm_caps.fw_type = WSM_GET16(buf); + priv->wsm_caps.fw_api = WSM_GET16(buf); + priv->wsm_caps.fw_build = WSM_GET16(buf); + priv->wsm_caps.fw_ver = WSM_GET16(buf); + WSM_GET(buf, priv->wsm_caps.fw_label, sizeof(priv->wsm_caps.fw_label)); + priv->wsm_caps.fw_label[sizeof(priv->wsm_caps.fw_label) - 1] = 0; /* Do not trust FW too much... */ + + if (WARN_ON(priv->wsm_caps.status)) + return -EINVAL; + + if (WARN_ON(priv->wsm_caps.fw_type > 4)) + return -EINVAL; + + pr_info("CW1200 WSM init done.\n" + " Input buffers: %d x %d bytes\n" + " Hardware: %d.%d\n" + " %s firmware [%s], ver: %d, build: %d," + " api: %d, cap: 0x%.4X\n", + priv->wsm_caps.input_buffers, + priv->wsm_caps.input_buffer_size, + priv->wsm_caps.hw_id, priv->wsm_caps.hw_subid, + cw1200_fw_types[priv->wsm_caps.fw_type], + priv->wsm_caps.fw_label, priv->wsm_caps.fw_ver, + priv->wsm_caps.fw_build, + priv->wsm_caps.fw_api, priv->wsm_caps.fw_cap); + + /* Disable unsupported frequency bands */ + if (!(priv->wsm_caps.fw_cap & 0x1)) + priv->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = NULL; + if (!(priv->wsm_caps.fw_cap & 0x2)) + priv->hw->wiphy->bands[IEEE80211_BAND_5GHZ] = NULL; + + priv->firmware_ready = 1; + wake_up(&priv->wsm_startup_done); + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +static int wsm_receive_indication(struct cw1200_common *priv, + int link_id, + struct wsm_buf *buf, + struct sk_buff **skb_p) +{ + struct wsm_rx rx; + struct ieee80211_hdr *hdr; + size_t hdr_len; + __le16 fctl; + + rx.status = WSM_GET32(buf); + rx.channel_number = WSM_GET16(buf); + rx.rx_rate = WSM_GET8(buf); + rx.rcpi_rssi = WSM_GET8(buf); + rx.flags = WSM_GET32(buf); + + /* FW Workaround: Drop probe resp or + beacon when RSSI is 0 + */ + hdr = (struct ieee80211_hdr *)(*skb_p)->data; + + if (!rx.rcpi_rssi && + (ieee80211_is_probe_resp(hdr->frame_control) || + ieee80211_is_beacon(hdr->frame_control))) + return 0; + + /* If no RSSI subscription has been made, + * convert RCPI to RSSI here + */ + if (!priv->cqm_use_rssi) + rx.rcpi_rssi = rx.rcpi_rssi / 2 - 110; + + fctl = *(__le16 *)buf->data; + hdr_len = buf->data - buf->begin; + skb_pull(*skb_p, hdr_len); + if (!rx.status && ieee80211_is_deauth(fctl)) { + if (priv->join_status == CW1200_JOIN_STATUS_STA) { + /* Shedule unjoin work */ + pr_debug("[WSM] Issue unjoin command (RX).\n"); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } + } + cw1200_rx_cb(priv, &rx, link_id, skb_p); + if (*skb_p) + skb_push(*skb_p, hdr_len); + + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_event_indication(struct cw1200_common *priv, struct wsm_buf *buf) +{ + int first; + struct cw1200_wsm_event *event; + + if (priv->mode == NL80211_IFTYPE_UNSPECIFIED) { + /* STA is stopped. */ + return 0; + } + + event = kzalloc(sizeof(struct cw1200_wsm_event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->evt.id = WSM_GET32(buf); + event->evt.data = WSM_GET32(buf); + + pr_debug("[WSM] Event: %d(%d)\n", + event->evt.id, event->evt.data); + + spin_lock(&priv->event_queue_lock); + first = list_empty(&priv->event_queue); + list_add_tail(&event->link, &priv->event_queue); + spin_unlock(&priv->event_queue_lock); + + if (first) + queue_work(priv->workqueue, &priv->event_handler); + + return 0; + +underflow: + kfree(event); + return -EINVAL; +} + +static int wsm_channel_switch_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + WARN_ON(WSM_GET32(buf)); + + priv->channel_switch_in_progress = 0; + wake_up(&priv->channel_switch_done); + + wsm_unlock_tx(priv); + + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_set_pm_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + /* TODO: Check buf (struct wsm_set_pm_complete) for validity */ + if (priv->ps_mode_switch_in_progress) { + priv->ps_mode_switch_in_progress = 0; + wake_up(&priv->ps_mode_switch_done); + } + return 0; +} + +static int wsm_scan_started(struct cw1200_common *priv, void *arg, + struct wsm_buf *buf) +{ + u32 status = WSM_GET32(buf); + if (status != WSM_STATUS_SUCCESS) { + cw1200_scan_failed_cb(priv); + return -EINVAL; + } + return 0; + +underflow: + WARN_ON(1); + return -EINVAL; +} + +static int wsm_scan_complete_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + struct wsm_scan_complete arg; + arg.status = WSM_GET32(buf); + arg.psm = WSM_GET8(buf); + arg.num_channels = WSM_GET8(buf); + cw1200_scan_complete_cb(priv, &arg); + + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_join_complete_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + struct wsm_join_complete arg; + arg.status = WSM_GET32(buf); + pr_debug("[WSM] Join complete indication, status: %d\n", arg.status); + cw1200_join_complete_cb(priv, &arg); + + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_find_complete_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + pr_warn("Implement find_complete_indication\n"); + return 0; +} + +static int wsm_ba_timeout_indication(struct cw1200_common *priv, + struct wsm_buf *buf) +{ + u32 dummy; + u8 tid; + u8 dummy2; + u8 addr[ETH_ALEN]; + + dummy = WSM_GET32(buf); + tid = WSM_GET8(buf); + dummy2 = WSM_GET8(buf); + WSM_GET(buf, addr, ETH_ALEN); + + pr_info("BlockACK timeout, tid %d, addr %pM\n", + tid, addr); + + return 0; + +underflow: + return -EINVAL; +} + +static int wsm_suspend_resume_indication(struct cw1200_common *priv, + int link_id, struct wsm_buf *buf) +{ + u32 flags; + struct wsm_suspend_resume arg; + + flags = WSM_GET32(buf); + arg.link_id = link_id; + arg.stop = !(flags & 1); + arg.multicast = !!(flags & 8); + arg.queue = (flags >> 1) & 3; + + cw1200_suspend_resume(priv, &arg); + + return 0; + +underflow: + return -EINVAL; +} + + +/* ******************************************************************** */ +/* WSM TX */ + +static int wsm_cmd_send(struct cw1200_common *priv, + struct wsm_buf *buf, + void *arg, u16 cmd, long tmo) +{ + size_t buf_len = buf->data - buf->begin; + int ret; + + /* Don't bother if we're dead. */ + if (priv->bh_error) { + ret = 0; + goto done; + } + + /* Block until the cmd buffer is completed. Tortuous. */ + spin_lock(&priv->wsm_cmd.lock); + while (!priv->wsm_cmd.done) { + spin_unlock(&priv->wsm_cmd.lock); + spin_lock(&priv->wsm_cmd.lock); + } + priv->wsm_cmd.done = 0; + spin_unlock(&priv->wsm_cmd.lock); + + if (cmd == WSM_WRITE_MIB_REQ_ID || + cmd == WSM_READ_MIB_REQ_ID) + pr_debug("[WSM] >>> 0x%.4X [MIB: 0x%.4X] (%zu)\n", + cmd, __le16_to_cpu(((__le16 *)buf->begin)[2]), + buf_len); + else + pr_debug("[WSM] >>> 0x%.4X (%zu)\n", cmd, buf_len); + + /* Due to buggy SPI on CW1200, we need to + * pad the message by a few bytes to ensure + * that it's completely received. + */ + buf_len += 4; + + /* Fill HI message header */ + /* BH will add sequence number */ + ((__le16 *)buf->begin)[0] = __cpu_to_le16(buf_len); + ((__le16 *)buf->begin)[1] = __cpu_to_le16(cmd); + + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(priv->wsm_cmd.ptr); + priv->wsm_cmd.ptr = buf->begin; + priv->wsm_cmd.len = buf_len; + priv->wsm_cmd.arg = arg; + priv->wsm_cmd.cmd = cmd; + spin_unlock(&priv->wsm_cmd.lock); + + cw1200_bh_wakeup(priv); + + /* Wait for command completion */ + ret = wait_event_timeout(priv->wsm_cmd_wq, + priv->wsm_cmd.done, tmo); + + if (!ret && !priv->wsm_cmd.done) { + spin_lock(&priv->wsm_cmd.lock); + priv->wsm_cmd.done = 1; + priv->wsm_cmd.ptr = NULL; + spin_unlock(&priv->wsm_cmd.lock); + if (priv->bh_error) { + /* Return ok to help system cleanup */ + ret = 0; + } else { + pr_err("CMD req (0x%04x) stuck in firmware, killing BH\n", priv->wsm_cmd.cmd); + print_hex_dump_bytes("REQDUMP: ", DUMP_PREFIX_NONE, + buf->begin, buf_len); + pr_err("Outstanding outgoing frames: %d\n", priv->hw_bufs_used); + + /* Kill BH thread to report the error to the top layer. */ + atomic_add(1, &priv->bh_term); + wake_up(&priv->bh_wq); + ret = -ETIMEDOUT; + } + } else { + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(!priv->wsm_cmd.done); + ret = priv->wsm_cmd.ret; + spin_unlock(&priv->wsm_cmd.lock); + } +done: + wsm_buf_reset(buf); + return ret; +} + +/* ******************************************************************** */ +/* WSM TX port control */ + +void wsm_lock_tx(struct cw1200_common *priv) +{ + wsm_cmd_lock(priv); + if (atomic_add_return(1, &priv->tx_lock) == 1) { + if (wsm_flush_tx(priv)) + pr_debug("[WSM] TX is locked.\n"); + } + wsm_cmd_unlock(priv); +} + +void wsm_lock_tx_async(struct cw1200_common *priv) +{ + if (atomic_add_return(1, &priv->tx_lock) == 1) + pr_debug("[WSM] TX is locked (async).\n"); +} + +bool wsm_flush_tx(struct cw1200_common *priv) +{ + unsigned long timestamp = jiffies; + bool pending = false; + long timeout; + int i; + + /* Flush must be called with TX lock held. */ + BUG_ON(!atomic_read(&priv->tx_lock)); + + /* First check if we really need to do something. + * It is safe to use unprotected access, as hw_bufs_used + * can only decrements. + */ + if (!priv->hw_bufs_used) + return true; + + if (priv->bh_error) { + /* In case of failure do not wait for magic. */ + pr_err("[WSM] Fatal error occurred, will not flush TX.\n"); + return false; + } else { + /* Get a timestamp of "oldest" frame */ + for (i = 0; i < 4; ++i) + pending |= cw1200_queue_get_xmit_timestamp( + &priv->tx_queue[i], + ×tamp, 0xffffffff); + /* If there's nothing pending, we're good */ + if (!pending) + return true; + + timeout = timestamp + WSM_CMD_LAST_CHANCE_TIMEOUT - jiffies; + if (timeout < 0 || wait_event_timeout(priv->bh_evt_wq, + !priv->hw_bufs_used, + timeout) <= 0) { + /* Hmmm... Not good. Frame had stuck in firmware. */ + priv->bh_error = 1; + wiphy_err(priv->hw->wiphy, "[WSM] TX Frames (%d) stuck in firmware, killing BH\n", priv->hw_bufs_used); + wake_up(&priv->bh_wq); + return false; + } + + /* Ok, everything is flushed. */ + return true; + } +} + +void wsm_unlock_tx(struct cw1200_common *priv) +{ + int tx_lock; + tx_lock = atomic_sub_return(1, &priv->tx_lock); + BUG_ON(tx_lock < 0); + + if (tx_lock == 0) { + if (!priv->bh_error) + cw1200_bh_wakeup(priv); + pr_debug("[WSM] TX is unlocked.\n"); + } +} + +/* ******************************************************************** */ +/* WSM RX */ + +int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len) +{ + struct wsm_buf buf; + u32 reason; + u32 reg[18]; + char fname[48]; + unsigned int i; + + static const char * const reason_str[] = { + "undefined instruction", + "prefetch abort", + "data abort", + "unknown error", + }; + + buf.begin = buf.data = data; + buf.end = &buf.begin[len]; + + reason = WSM_GET32(&buf); + for (i = 0; i < ARRAY_SIZE(reg); ++i) + reg[i] = WSM_GET32(&buf); + WSM_GET(&buf, fname, sizeof(fname)); + + if (reason < 4) + wiphy_err(priv->hw->wiphy, + "Firmware exception: %s.\n", + reason_str[reason]); + else + wiphy_err(priv->hw->wiphy, + "Firmware assert at %.*s, line %d\n", + (int) sizeof(fname), fname, reg[1]); + + for (i = 0; i < 12; i += 4) + wiphy_err(priv->hw->wiphy, + "R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X, R%d: 0x%.8X,\n", + i + 0, reg[i + 0], i + 1, reg[i + 1], + i + 2, reg[i + 2], i + 3, reg[i + 3]); + wiphy_err(priv->hw->wiphy, + "R12: 0x%.8X, SP: 0x%.8X, LR: 0x%.8X, PC: 0x%.8X,\n", + reg[i + 0], reg[i + 1], reg[i + 2], reg[i + 3]); + i += 4; + wiphy_err(priv->hw->wiphy, + "CPSR: 0x%.8X, SPSR: 0x%.8X\n", + reg[i + 0], reg[i + 1]); + + print_hex_dump_bytes("R1: ", DUMP_PREFIX_NONE, + fname, sizeof(fname)); + return 0; + +underflow: + wiphy_err(priv->hw->wiphy, "Firmware exception.\n"); + print_hex_dump_bytes("Exception: ", DUMP_PREFIX_NONE, + data, len); + return -EINVAL; +} + +int wsm_handle_rx(struct cw1200_common *priv, u16 id, + struct wsm_hdr *wsm, struct sk_buff **skb_p) +{ + int ret = 0; + struct wsm_buf wsm_buf; + int link_id = (id >> 6) & 0x0F; + + /* Strip link id. */ + id &= ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX); + + wsm_buf.begin = (u8 *)&wsm[0]; + wsm_buf.data = (u8 *)&wsm[1]; + wsm_buf.end = &wsm_buf.begin[__le16_to_cpu(wsm->len)]; + + pr_debug("[WSM] <<< 0x%.4X (%td)\n", id, + wsm_buf.end - wsm_buf.begin); + + if (id == WSM_TX_CONFIRM_IND_ID) { + ret = wsm_tx_confirm(priv, &wsm_buf, link_id); + } else if (id == WSM_MULTI_TX_CONFIRM_ID) { + ret = wsm_multi_tx_confirm(priv, &wsm_buf, link_id); + } else if (id & 0x0400) { + void *wsm_arg; + u16 wsm_cmd; + + /* Do not trust FW too much. Protection against repeated + * response and race condition removal (see above). + */ + spin_lock(&priv->wsm_cmd.lock); + wsm_arg = priv->wsm_cmd.arg; + wsm_cmd = priv->wsm_cmd.cmd & + ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX); + priv->wsm_cmd.cmd = 0xFFFF; + spin_unlock(&priv->wsm_cmd.lock); + + if (WARN_ON((id & ~0x0400) != wsm_cmd)) { + /* Note that any non-zero is a fatal retcode. */ + ret = -EINVAL; + goto out; + } + + /* Note that wsm_arg can be NULL in case of timeout in + * wsm_cmd_send(). + */ + + switch (id) { + case WSM_READ_MIB_RESP_ID: + if (wsm_arg) + ret = wsm_read_mib_confirm(priv, wsm_arg, + &wsm_buf); + break; + case WSM_WRITE_MIB_RESP_ID: + if (wsm_arg) + ret = wsm_write_mib_confirm(priv, wsm_arg, + &wsm_buf); + break; + case WSM_START_SCAN_RESP_ID: + if (wsm_arg) + ret = wsm_scan_started(priv, wsm_arg, &wsm_buf); + break; + case WSM_CONFIGURATION_RESP_ID: + if (wsm_arg) + ret = wsm_configuration_confirm(priv, wsm_arg, + &wsm_buf); + break; + case WSM_JOIN_RESP_ID: + if (wsm_arg) + ret = wsm_join_confirm(priv, wsm_arg, &wsm_buf); + break; + case WSM_STOP_SCAN_RESP_ID: + case WSM_RESET_RESP_ID: + case WSM_ADD_KEY_RESP_ID: + case WSM_REMOVE_KEY_RESP_ID: + case WSM_SET_PM_RESP_ID: + case WSM_SET_BSS_PARAMS_RESP_ID: + case 0x0412: /* set_tx_queue_params */ + case WSM_EDCA_PARAMS_RESP_ID: + case WSM_SWITCH_CHANNEL_RESP_ID: + case WSM_START_RESP_ID: + case WSM_BEACON_TRANSMIT_RESP_ID: + case 0x0419: /* start_find */ + case 0x041A: /* stop_find */ + case 0x041B: /* update_ie */ + case 0x041C: /* map_link */ + WARN_ON(wsm_arg != NULL); + ret = wsm_generic_confirm(priv, wsm_arg, &wsm_buf); + if (ret) { + wiphy_warn(priv->hw->wiphy, + "wsm_generic_confirm failed for request 0x%04x.\n", + id & ~0x0400); + + /* often 0x407 and 0x410 occur, this means we're dead.. */ + if (priv->join_status >= CW1200_JOIN_STATUS_JOINING) { + wsm_lock_tx(priv); + if (queue_work(priv->workqueue, &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } + } + break; + default: + wiphy_warn(priv->hw->wiphy, + "Unrecognized confirmation 0x%04x\n", + id & ~0x0400); + } + + spin_lock(&priv->wsm_cmd.lock); + priv->wsm_cmd.ret = ret; + priv->wsm_cmd.done = 1; + spin_unlock(&priv->wsm_cmd.lock); + + ret = 0; /* Error response from device should ne stop BH. */ + + wake_up(&priv->wsm_cmd_wq); + } else if (id & 0x0800) { + switch (id) { + case WSM_STARTUP_IND_ID: + ret = wsm_startup_indication(priv, &wsm_buf); + break; + case WSM_RECEIVE_IND_ID: + ret = wsm_receive_indication(priv, link_id, + &wsm_buf, skb_p); + break; + case 0x0805: + ret = wsm_event_indication(priv, &wsm_buf); + break; + case WSM_SCAN_COMPLETE_IND_ID: + ret = wsm_scan_complete_indication(priv, &wsm_buf); + break; + case 0x0808: + ret = wsm_ba_timeout_indication(priv, &wsm_buf); + break; + case 0x0809: + ret = wsm_set_pm_indication(priv, &wsm_buf); + break; + case 0x080A: + ret = wsm_channel_switch_indication(priv, &wsm_buf); + break; + case 0x080B: + ret = wsm_find_complete_indication(priv, &wsm_buf); + break; + case 0x080C: + ret = wsm_suspend_resume_indication(priv, + link_id, &wsm_buf); + break; + case 0x080F: + ret = wsm_join_complete_indication(priv, &wsm_buf); + break; + default: + pr_warn("Unrecognised WSM ID %04x\n", id); + } + } else { + WARN_ON(1); + ret = -EINVAL; + } +out: + return ret; +} + +static bool wsm_handle_tx_data(struct cw1200_common *priv, + struct wsm_tx *wsm, + const struct ieee80211_tx_info *tx_info, + const struct cw1200_txpriv *txpriv, + struct cw1200_queue *queue) +{ + bool handled = false; + const struct ieee80211_hdr *frame = + (struct ieee80211_hdr *)&((u8 *)wsm)[txpriv->offset]; + __le16 fctl = frame->frame_control; + enum { + do_probe, + do_drop, + do_wep, + do_tx, + } action = do_tx; + + switch (priv->mode) { + case NL80211_IFTYPE_STATION: + if (priv->join_status == CW1200_JOIN_STATUS_MONITOR) + action = do_tx; + else if (priv->join_status < CW1200_JOIN_STATUS_PRE_STA) + action = do_drop; + break; + case NL80211_IFTYPE_AP: + if (!priv->join_status) { + action = do_drop; + } else if (!(BIT(txpriv->raw_link_id) & + (BIT(0) | priv->link_id_map))) { + wiphy_warn(priv->hw->wiphy, + "A frame with expired link id is dropped.\n"); + action = do_drop; + } + if (cw1200_queue_get_generation(wsm->packet_id) > + CW1200_MAX_REQUEUE_ATTEMPTS) { + /* HACK!!! WSM324 firmware has tendency to requeue + * multicast frames in a loop, causing performance + * drop and high power consumption of the driver. + * In this situation it is better just to drop + * the problematic frame. + */ + wiphy_warn(priv->hw->wiphy, + "Too many attempts to requeue a frame; dropped.\n"); + action = do_drop; + } + break; + case NL80211_IFTYPE_ADHOC: + if (priv->join_status != CW1200_JOIN_STATUS_IBSS) + action = do_drop; + break; + case NL80211_IFTYPE_MESH_POINT: + action = do_tx; /* TODO: Test me! */ + break; + case NL80211_IFTYPE_MONITOR: + default: + action = do_drop; + break; + } + + if (action == do_tx) { + if (ieee80211_is_nullfunc(fctl)) { + spin_lock(&priv->bss_loss_lock); + if (priv->bss_loss_state) { + priv->bss_loss_confirm_id = wsm->packet_id; + wsm->queue_id = WSM_QUEUE_VOICE; + } + spin_unlock(&priv->bss_loss_lock); + } else if (ieee80211_is_probe_req(fctl)) { + action = do_probe; + } else if (ieee80211_is_deauth(fctl) && + priv->mode != NL80211_IFTYPE_AP) { + pr_debug("[WSM] Issue unjoin command due to tx deauth.\n"); + wsm_lock_tx_async(priv); + if (queue_work(priv->workqueue, + &priv->unjoin_work) <= 0) + wsm_unlock_tx(priv); + } else if (ieee80211_has_protected(fctl) && + tx_info->control.hw_key && + tx_info->control.hw_key->keyidx != priv->wep_default_key_id && + (tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_WEP40 || + tx_info->control.hw_key->cipher == WLAN_CIPHER_SUITE_WEP104)) { + action = do_wep; + } + } + + switch (action) { + case do_probe: + /* An interesting FW "feature". Device filters probe responses. + * The easiest way to get it back is to convert + * probe request into WSM start_scan command. + */ + pr_debug("[WSM] Convert probe request to scan.\n"); + wsm_lock_tx_async(priv); + priv->pending_frame_id = wsm->packet_id; + if (queue_delayed_work(priv->workqueue, + &priv->scan.probe_work, 0) <= 0) + wsm_unlock_tx(priv); + handled = true; + break; + case do_drop: + pr_debug("[WSM] Drop frame (0x%.4X).\n", fctl); + BUG_ON(cw1200_queue_remove(queue, wsm->packet_id)); + handled = true; + break; + case do_wep: + pr_debug("[WSM] Issue set_default_wep_key.\n"); + wsm_lock_tx_async(priv); + priv->wep_default_key_id = tx_info->control.hw_key->keyidx; + priv->pending_frame_id = wsm->packet_id; + if (queue_work(priv->workqueue, &priv->wep_key_work) <= 0) + wsm_unlock_tx(priv); + handled = true; + break; + case do_tx: + pr_debug("[WSM] Transmit frame.\n"); + break; + default: + /* Do nothing */ + break; + } + return handled; +} + +static int cw1200_get_prio_queue(struct cw1200_common *priv, + u32 link_id_map, int *total) +{ + static const int urgent = BIT(CW1200_LINK_ID_AFTER_DTIM) | + BIT(CW1200_LINK_ID_UAPSD); + struct wsm_edca_queue_params *edca; + unsigned score, best = -1; + int winner = -1; + int queued; + int i; + + /* search for a winner using edca params */ + for (i = 0; i < 4; ++i) { + queued = cw1200_queue_get_num_queued(&priv->tx_queue[i], + link_id_map); + if (!queued) + continue; + *total += queued; + edca = &priv->edca.params[i]; + score = ((edca->aifns + edca->cwmin) << 16) + + ((edca->cwmax - edca->cwmin) * + (get_random_int() & 0xFFFF)); + if (score < best && (winner < 0 || i != 3)) { + best = score; + winner = i; + } + } + + /* override winner if bursting */ + if (winner >= 0 && priv->tx_burst_idx >= 0 && + winner != priv->tx_burst_idx && + !cw1200_queue_get_num_queued( + &priv->tx_queue[winner], + link_id_map & urgent) && + cw1200_queue_get_num_queued( + &priv->tx_queue[priv->tx_burst_idx], + link_id_map)) + winner = priv->tx_burst_idx; + + return winner; +} + +static int wsm_get_tx_queue_and_mask(struct cw1200_common *priv, + struct cw1200_queue **queue_p, + u32 *tx_allowed_mask_p, + bool *more) +{ + int idx; + u32 tx_allowed_mask; + int total = 0; + + /* Search for a queue with multicast frames buffered */ + if (priv->tx_multicast) { + tx_allowed_mask = BIT(CW1200_LINK_ID_AFTER_DTIM); + idx = cw1200_get_prio_queue(priv, + tx_allowed_mask, &total); + if (idx >= 0) { + *more = total > 1; + goto found; + } + } + + /* Search for unicast traffic */ + tx_allowed_mask = ~priv->sta_asleep_mask; + tx_allowed_mask |= BIT(CW1200_LINK_ID_UAPSD); + if (priv->sta_asleep_mask) { + tx_allowed_mask |= priv->pspoll_mask; + tx_allowed_mask &= ~BIT(CW1200_LINK_ID_AFTER_DTIM); + } else { + tx_allowed_mask |= BIT(CW1200_LINK_ID_AFTER_DTIM); + } + idx = cw1200_get_prio_queue(priv, + tx_allowed_mask, &total); + if (idx < 0) + return -ENOENT; + +found: + *queue_p = &priv->tx_queue[idx]; + *tx_allowed_mask_p = tx_allowed_mask; + return 0; +} + +int wsm_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst) +{ + struct wsm_tx *wsm = NULL; + struct ieee80211_tx_info *tx_info; + struct cw1200_queue *queue = NULL; + int queue_num; + u32 tx_allowed_mask = 0; + const struct cw1200_txpriv *txpriv = NULL; + int count = 0; + + /* More is used only for broadcasts. */ + bool more = false; + + if (priv->wsm_cmd.ptr) { /* CMD request */ + ++count; + spin_lock(&priv->wsm_cmd.lock); + BUG_ON(!priv->wsm_cmd.ptr); + *data = priv->wsm_cmd.ptr; + *tx_len = priv->wsm_cmd.len; + *burst = 1; + spin_unlock(&priv->wsm_cmd.lock); + } else { + for (;;) { + int ret; + + if (atomic_add_return(0, &priv->tx_lock)) + break; + + spin_lock_bh(&priv->ps_state_lock); + + ret = wsm_get_tx_queue_and_mask(priv, &queue, + &tx_allowed_mask, &more); + queue_num = queue - priv->tx_queue; + + if (priv->buffered_multicasts && + (ret || !more) && + (priv->tx_multicast || !priv->sta_asleep_mask)) { + priv->buffered_multicasts = false; + if (priv->tx_multicast) { + priv->tx_multicast = false; + queue_work(priv->workqueue, + &priv->multicast_stop_work); + } + } + + spin_unlock_bh(&priv->ps_state_lock); + + if (ret) + break; + + if (cw1200_queue_get(queue, + tx_allowed_mask, + &wsm, &tx_info, &txpriv)) + continue; + + if (wsm_handle_tx_data(priv, wsm, + tx_info, txpriv, queue)) + continue; /* Handled by WSM */ + + wsm->hdr.id &= __cpu_to_le16( + ~WSM_TX_LINK_ID(WSM_TX_LINK_ID_MAX)); + wsm->hdr.id |= cpu_to_le16( + WSM_TX_LINK_ID(txpriv->raw_link_id)); + priv->pspoll_mask &= ~BIT(txpriv->raw_link_id); + + *data = (u8 *)wsm; + *tx_len = __le16_to_cpu(wsm->hdr.len); + + /* allow bursting if txop is set */ + if (priv->edca.params[queue_num].txop_limit) + *burst = min(*burst, + (int)cw1200_queue_get_num_queued(queue, tx_allowed_mask) + 1); + else + *burst = 1; + + /* store index of bursting queue */ + if (*burst > 1) + priv->tx_burst_idx = queue_num; + else + priv->tx_burst_idx = -1; + + if (more) { + struct ieee80211_hdr *hdr = + (struct ieee80211_hdr *) + &((u8 *)wsm)[txpriv->offset]; + /* more buffered multicast/broadcast frames + * ==> set MoreData flag in IEEE 802.11 header + * to inform PS STAs + */ + hdr->frame_control |= + cpu_to_le16(IEEE80211_FCTL_MOREDATA); + } + + pr_debug("[WSM] >>> 0x%.4X (%zu) %p %c\n", + 0x0004, *tx_len, *data, + wsm->more ? 'M' : ' '); + ++count; + break; + } + } + + return count; +} + +void wsm_txed(struct cw1200_common *priv, u8 *data) +{ + if (data == priv->wsm_cmd.ptr) { + spin_lock(&priv->wsm_cmd.lock); + priv->wsm_cmd.ptr = NULL; + spin_unlock(&priv->wsm_cmd.lock); + } +} + +/* ******************************************************************** */ +/* WSM buffer */ + +void wsm_buf_init(struct wsm_buf *buf) +{ + BUG_ON(buf->begin); + buf->begin = kmalloc(FWLOAD_BLOCK_SIZE, GFP_KERNEL | GFP_DMA); + buf->end = buf->begin ? &buf->begin[FWLOAD_BLOCK_SIZE] : buf->begin; + wsm_buf_reset(buf); +} + +void wsm_buf_deinit(struct wsm_buf *buf) +{ + kfree(buf->begin); + buf->begin = buf->data = buf->end = NULL; +} + +static void wsm_buf_reset(struct wsm_buf *buf) +{ + if (buf->begin) { + buf->data = &buf->begin[4]; + *(u32 *)buf->begin = 0; + } else { + buf->data = buf->begin; + } +} + +static int wsm_buf_reserve(struct wsm_buf *buf, size_t extra_size) +{ + size_t pos = buf->data - buf->begin; + size_t size = pos + extra_size; + + size = round_up(size, FWLOAD_BLOCK_SIZE); + + buf->begin = krealloc(buf->begin, size, GFP_KERNEL | GFP_DMA); + if (buf->begin) { + buf->data = &buf->begin[pos]; + buf->end = &buf->begin[size]; + return 0; + } else { + buf->end = buf->data = buf->begin; + return -ENOMEM; + } +} diff --git a/drivers/net/wireless/acc1340/wsm.h b/drivers/net/wireless/acc1340/wsm.h new file mode 100644 index 0000000000000..48086e849515b --- /dev/null +++ b/drivers/net/wireless/acc1340/wsm.h @@ -0,0 +1,1870 @@ +/* + * WSM host interface (HI) interface for ST-Ericsson CW1200 mac80211 drivers + * + * Copyright (c) 2010, ST-Ericsson + * Author: Dmitry Tarnyagin + * + * Based on CW1200 UMAC WSM API, which is + * Copyright (C) ST-Ericsson SA 2010 + * Author: Stewart Mathers + * + * 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 CW1200_WSM_H_INCLUDED +#define CW1200_WSM_H_INCLUDED + +#include + +struct cw1200_common; + +/* Bands */ +/* Radio band 2.412 -2.484 GHz. */ +#define WSM_PHY_BAND_2_4G (0) + +/* Radio band 4.9375-5.8250 GHz. */ +#define WSM_PHY_BAND_5G (1) + +/* Transmit rates */ +/* 1 Mbps ERP-DSSS */ +#define WSM_TRANSMIT_RATE_1 (0) + +/* 2 Mbps ERP-DSSS */ +#define WSM_TRANSMIT_RATE_2 (1) + +/* 5.5 Mbps ERP-CCK */ +#define WSM_TRANSMIT_RATE_5 (2) + +/* 11 Mbps ERP-CCK */ +#define WSM_TRANSMIT_RATE_11 (3) + +/* 22 Mbps ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_22 (4) */ + +/* 33 Mbps ERP-PBCC (Not supported) */ +/* #define WSM_TRANSMIT_RATE_33 (5) */ + +/* 6 Mbps (3 Mbps) ERP-OFDM, BPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_6 (6) + +/* 9 Mbps (4.5 Mbps) ERP-OFDM, BPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_9 (7) + +/* 12 Mbps (6 Mbps) ERP-OFDM, QPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_12 (8) + +/* 18 Mbps (9 Mbps) ERP-OFDM, QPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_18 (9) + +/* 24 Mbps (12 Mbps) ERP-OFDM, 16QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_24 (10) + +/* 36 Mbps (18 Mbps) ERP-OFDM, 16QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_36 (11) + +/* 48 Mbps (24 Mbps) ERP-OFDM, 64QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_48 (12) + +/* 54 Mbps (27 Mbps) ERP-OFDM, 64QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_54 (13) + +/* 6.5 Mbps HT-OFDM, BPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_6 (14) + +/* 13 Mbps HT-OFDM, QPSK coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_13 (15) + +/* 19.5 Mbps HT-OFDM, QPSK coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_19 (16) + +/* 26 Mbps HT-OFDM, 16QAM coding rate 1/2 */ +#define WSM_TRANSMIT_RATE_HT_26 (17) + +/* 39 Mbps HT-OFDM, 16QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_39 (18) + +/* 52 Mbps HT-OFDM, 64QAM coding rate 2/3 */ +#define WSM_TRANSMIT_RATE_HT_52 (19) + +/* 58.5 Mbps HT-OFDM, 64QAM coding rate 3/4 */ +#define WSM_TRANSMIT_RATE_HT_58 (20) + +/* 65 Mbps HT-OFDM, 64QAM coding rate 5/6 */ +#define WSM_TRANSMIT_RATE_HT_65 (21) + +/* Scan types */ +/* Foreground scan */ +#define WSM_SCAN_TYPE_FOREGROUND (0) + +/* Background scan */ +#define WSM_SCAN_TYPE_BACKGROUND (1) + +/* Auto scan */ +#define WSM_SCAN_TYPE_AUTO (2) + +/* Scan flags */ +/* Forced background scan means if the station cannot */ +/* enter the power-save mode, it shall force to perform a */ +/* background scan. Only valid when ScanType is */ +/* background scan. */ +#define WSM_SCAN_FLAG_FORCE_BACKGROUND (BIT(0)) + +/* The WLAN device scans one channel at a time so */ +/* that disturbance to the data traffic is minimized. */ +#define WSM_SCAN_FLAG_SPLIT_METHOD (BIT(1)) + +/* Preamble Type. Long if not set. */ +#define WSM_SCAN_FLAG_SHORT_PREAMBLE (BIT(2)) + +/* 11n Tx Mode. Mixed if not set. */ +#define WSM_SCAN_FLAG_11N_GREENFIELD (BIT(3)) + +/* Scan constraints */ +/* Maximum number of channels to be scanned. */ +#define WSM_SCAN_MAX_NUM_OF_CHANNELS (48) + +/* The maximum number of SSIDs that the device can scan for. */ +#define WSM_SCAN_MAX_NUM_OF_SSIDS (2) + +/* Power management modes */ +/* 802.11 Active mode */ +#define WSM_PSM_ACTIVE (0) + +/* 802.11 PS mode */ +#define WSM_PSM_PS BIT(0) + +/* Fast Power Save bit */ +#define WSM_PSM_FAST_PS_FLAG BIT(7) + +/* Dynamic aka Fast power save */ +#define WSM_PSM_FAST_PS (BIT(0) | BIT(7)) + +/* Undetermined */ +/* Note : Undetermined status is reported when the */ +/* NULL data frame used to advertise the PM mode to */ +/* the AP at Pre or Post Background Scan is not Acknowledged */ +#define WSM_PSM_UNKNOWN BIT(1) + +/* Queue IDs */ +/* best effort/legacy */ +#define WSM_QUEUE_BEST_EFFORT (0) + +/* background */ +#define WSM_QUEUE_BACKGROUND (1) + +/* video */ +#define WSM_QUEUE_VIDEO (2) + +/* voice */ +#define WSM_QUEUE_VOICE (3) + +/* HT TX parameters */ +/* Non-HT */ +#define WSM_HT_TX_NON_HT (0) + +/* Mixed format */ +#define WSM_HT_TX_MIXED (1) + +/* Greenfield format */ +#define WSM_HT_TX_GREENFIELD (2) + +/* STBC allowed */ +#define WSM_HT_TX_STBC (BIT(7)) + +/* EPTA prioirty flags for BT Coex */ +/* default epta priority */ +#define WSM_EPTA_PRIORITY_DEFAULT 4 +/* use for normal data */ +#define WSM_EPTA_PRIORITY_DATA 4 +/* use for connect/disconnect/roaming*/ +#define WSM_EPTA_PRIORITY_MGT 5 +/* use for action frames */ +#define WSM_EPTA_PRIORITY_ACTION 5 +/* use for AC_VI data */ +#define WSM_EPTA_PRIORITY_VIDEO 5 +/* use for AC_VO data */ +#define WSM_EPTA_PRIORITY_VOICE 6 +/* use for EAPOL exchange */ +#define WSM_EPTA_PRIORITY_EAPOL 7 + +/* TX status */ +/* Frame was sent aggregated */ +/* Only valid for WSM_SUCCESS status. */ +#define WSM_TX_STATUS_AGGREGATION (BIT(0)) + +/* Host should requeue this frame later. */ +/* Valid only when status is WSM_REQUEUE. */ +#define WSM_TX_STATUS_REQUEUE (BIT(1)) + +/* Normal Ack */ +#define WSM_TX_STATUS_NORMAL_ACK (0<<2) + +/* No Ack */ +#define WSM_TX_STATUS_NO_ACK (1<<2) + +/* No explicit acknowledgement */ +#define WSM_TX_STATUS_NO_EXPLICIT_ACK (2<<2) + +/* Block Ack */ +/* Only valid for WSM_SUCCESS status. */ +#define WSM_TX_STATUS_BLOCK_ACK (3<<2) + +/* RX status */ +/* Unencrypted */ +#define WSM_RX_STATUS_UNENCRYPTED (0<<0) + +/* WEP */ +#define WSM_RX_STATUS_WEP (1<<0) + +/* TKIP */ +#define WSM_RX_STATUS_TKIP (2<<0) + +/* AES */ +#define WSM_RX_STATUS_AES (3<<0) + +/* WAPI */ +#define WSM_RX_STATUS_WAPI (4<<0) + +/* Macro to fetch encryption subfield. */ +#define WSM_RX_STATUS_ENCRYPTION(status) ((status) & 0x07) + +/* Frame was part of an aggregation */ +#define WSM_RX_STATUS_AGGREGATE (BIT(3)) + +/* Frame was first in the aggregation */ +#define WSM_RX_STATUS_AGGREGATE_FIRST (BIT(4)) + +/* Frame was last in the aggregation */ +#define WSM_RX_STATUS_AGGREGATE_LAST (BIT(5)) + +/* Indicates a defragmented frame */ +#define WSM_RX_STATUS_DEFRAGMENTED (BIT(6)) + +/* Indicates a Beacon frame */ +#define WSM_RX_STATUS_BEACON (BIT(7)) + +/* Indicates STA bit beacon TIM field */ +#define WSM_RX_STATUS_TIM (BIT(8)) + +/* Indicates Beacon frame's virtual bitmap contains multicast bit */ +#define WSM_RX_STATUS_MULTICAST (BIT(9)) + +/* Indicates frame contains a matching SSID */ +#define WSM_RX_STATUS_MATCHING_SSID (BIT(10)) + +/* Indicates frame contains a matching BSSI */ +#define WSM_RX_STATUS_MATCHING_BSSI (BIT(11)) + +/* Indicates More bit set in Framectl field */ +#define WSM_RX_STATUS_MORE_DATA (BIT(12)) + +/* Indicates frame received during a measurement process */ +#define WSM_RX_STATUS_MEASUREMENT (BIT(13)) + +/* Indicates frame received as an HT packet */ +#define WSM_RX_STATUS_HT (BIT(14)) + +/* Indicates frame received with STBC */ +#define WSM_RX_STATUS_STBC (BIT(15)) + +/* Indicates Address 1 field matches dot11StationId */ +#define WSM_RX_STATUS_ADDRESS1 (BIT(16)) + +/* Indicates Group address present in the Address 1 field */ +#define WSM_RX_STATUS_GROUP (BIT(17)) + +/* Indicates Broadcast address present in the Address 1 field */ +#define WSM_RX_STATUS_BROADCAST (BIT(18)) + +/* Indicates group key used with encrypted frames */ +#define WSM_RX_STATUS_GROUP_KEY (BIT(19)) + +/* Macro to fetch encryption key index. */ +#define WSM_RX_STATUS_KEY_IDX(status) (((status >> 20)) & 0x0F) + +/* Indicates TSF inclusion after 802.11 frame body */ +#define WSM_RX_STATUS_TSF_INCLUDED (BIT(24)) + +/* Frame Control field starts at Frame offset + 2 */ +#define WSM_TX_2BYTES_SHIFT (BIT(7)) + +/* Join mode */ +/* IBSS */ +#define WSM_JOIN_MODE_IBSS (0) + +/* BSS */ +#define WSM_JOIN_MODE_BSS (1) + +/* PLCP preamble type */ +/* For long preamble */ +#define WSM_JOIN_PREAMBLE_LONG (0) + +/* For short preamble (Long for 1Mbps) */ +#define WSM_JOIN_PREAMBLE_SHORT (1) + +/* For short preamble (Long for 1 and 2Mbps) */ +#define WSM_JOIN_PREAMBLE_SHORT_2 (2) + +/* Join flags */ +/* Unsynchronized */ +#define WSM_JOIN_FLAGS_UNSYNCRONIZED BIT(0) +/* The BSS owner is a P2P GO */ +#define WSM_JOIN_FLAGS_P2P_GO BIT(1) +/* Force to join BSS with the BSSID and the + * SSID specified without waiting for beacons. The + * ProbeForJoin parameter is ignored. + */ +#define WSM_JOIN_FLAGS_FORCE BIT(2) +/* Give probe request/response higher + * priority over the BT traffic + */ +#define WSM_JOIN_FLAGS_PRIO BIT(3) +/* Issue immediate join confirmation and use + * join complete to notify about completion + */ +#define WSM_JOIN_FLAGS_FORCE_WITH_COMPLETE_IND BIT(5) + +/* Key types */ +#define WSM_KEY_TYPE_WEP_DEFAULT (0) +#define WSM_KEY_TYPE_WEP_PAIRWISE (1) +#define WSM_KEY_TYPE_TKIP_GROUP (2) +#define WSM_KEY_TYPE_TKIP_PAIRWISE (3) +#define WSM_KEY_TYPE_AES_GROUP (4) +#define WSM_KEY_TYPE_AES_PAIRWISE (5) +#define WSM_KEY_TYPE_WAPI_GROUP (6) +#define WSM_KEY_TYPE_WAPI_PAIRWISE (7) + +/* Key indexes */ +#define WSM_KEY_MAX_INDEX (10) + +/* ACK policy */ +#define WSM_ACK_POLICY_NORMAL (0) +#define WSM_ACK_POLICY_NO_ACK (1) + +/* Start modes */ +#define WSM_START_MODE_AP (0) /* Mini AP */ +#define WSM_START_MODE_P2P_GO (1) /* P2P GO */ +#define WSM_START_MODE_P2P_DEV (2) /* P2P device */ + +/* SetAssociationMode MIB flags */ +#define WSM_ASSOCIATION_MODE_USE_PREAMBLE_TYPE (BIT(0)) +#define WSM_ASSOCIATION_MODE_USE_HT_MODE (BIT(1)) +#define WSM_ASSOCIATION_MODE_USE_BASIC_RATE_SET (BIT(2)) +#define WSM_ASSOCIATION_MODE_USE_MPDU_START_SPACING (BIT(3)) +#define WSM_ASSOCIATION_MODE_SNOOP_ASSOC_FRAMES (BIT(4)) + +/* RcpiRssiThreshold MIB flags */ +#define WSM_RCPI_RSSI_THRESHOLD_ENABLE (BIT(0)) +#define WSM_RCPI_RSSI_USE_RSSI (BIT(1)) +#define WSM_RCPI_RSSI_DONT_USE_UPPER (BIT(2)) +#define WSM_RCPI_RSSI_DONT_USE_LOWER (BIT(3)) + +/* Update-ie constants */ +#define WSM_UPDATE_IE_BEACON (BIT(0)) +#define WSM_UPDATE_IE_PROBE_RESP (BIT(1)) +#define WSM_UPDATE_IE_PROBE_REQ (BIT(2)) + +/* WSM events */ +/* Error */ +#define WSM_EVENT_ERROR (0) + +/* BSS lost */ +#define WSM_EVENT_BSS_LOST (1) + +/* BSS regained */ +#define WSM_EVENT_BSS_REGAINED (2) + +/* Radar detected */ +#define WSM_EVENT_RADAR_DETECTED (3) + +/* RCPI or RSSI threshold triggered */ +#define WSM_EVENT_RCPI_RSSI (4) + +/* BT inactive */ +#define WSM_EVENT_BT_INACTIVE (5) + +/* BT active */ +#define WSM_EVENT_BT_ACTIVE (6) + +/* MIB IDs */ +/* 4.1 dot11StationId */ +#define WSM_MIB_ID_DOT11_STATION_ID 0x0000 + +/* 4.2 dot11MaxtransmitMsduLifeTime */ +#define WSM_MIB_ID_DOT11_MAX_TRANSMIT_LIFTIME 0x0001 + +/* 4.3 dot11MaxReceiveLifeTime */ +#define WSM_MIB_ID_DOT11_MAX_RECEIVE_LIFETIME 0x0002 + +/* 4.4 dot11SlotTime */ +#define WSM_MIB_ID_DOT11_SLOT_TIME 0x0003 + +/* 4.5 dot11GroupAddressesTable */ +#define WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE 0x0004 +#define WSM_MAX_GRP_ADDRTABLE_ENTRIES 8 + +/* 4.6 dot11WepDefaultKeyId */ +#define WSM_MIB_ID_DOT11_WEP_DEFAULT_KEY_ID 0x0005 + +/* 4.7 dot11CurrentTxPowerLevel */ +#define WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL 0x0006 + +/* 4.8 dot11RTSThreshold */ +#define WSM_MIB_ID_DOT11_RTS_THRESHOLD 0x0007 + +/* 4.9 NonErpProtection */ +#define WSM_MIB_ID_NON_ERP_PROTECTION 0x1000 + +/* 4.10 ArpIpAddressesTable */ +#define WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE 0x1001 +#define WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES 1 + +/* 4.11 TemplateFrame */ +#define WSM_MIB_ID_TEMPLATE_FRAME 0x1002 + +/* 4.12 RxFilter */ +#define WSM_MIB_ID_RX_FILTER 0x1003 + +/* 4.13 BeaconFilterTable */ +#define WSM_MIB_ID_BEACON_FILTER_TABLE 0x1004 + +/* 4.14 BeaconFilterEnable */ +#define WSM_MIB_ID_BEACON_FILTER_ENABLE 0x1005 + +/* 4.15 OperationalPowerMode */ +#define WSM_MIB_ID_OPERATIONAL_POWER_MODE 0x1006 + +/* 4.16 BeaconWakeUpPeriod */ +#define WSM_MIB_ID_BEACON_WAKEUP_PERIOD 0x1007 + +/* 4.17 RcpiRssiThreshold */ +#define WSM_MIB_ID_RCPI_RSSI_THRESHOLD 0x1009 + +/* 4.18 StatisticsTable */ +#define WSM_MIB_ID_STATISTICS_TABLE 0x100A + +/* 4.19 IbssPsConfig */ +#define WSM_MIB_ID_IBSS_PS_CONFIG 0x100B + +/* 4.20 CountersTable */ +#define WSM_MIB_ID_COUNTERS_TABLE 0x100C + +/* 4.21 BlockAckPolicy */ +#define WSM_MIB_ID_BLOCK_ACK_POLICY 0x100E + +/* 4.22 OverrideInternalTxRate */ +#define WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE 0x100F + +/* 4.23 SetAssociationMode */ +#define WSM_MIB_ID_SET_ASSOCIATION_MODE 0x1010 + +/* 4.24 UpdateEptaConfigData */ +#define WSM_MIB_ID_UPDATE_EPTA_CONFIG_DATA 0x1011 + +/* 4.25 SelectCcaMethod */ +#define WSM_MIB_ID_SELECT_CCA_METHOD 0x1012 + +/* 4.26 SetUpasdInformation */ +#define WSM_MIB_ID_SET_UAPSD_INFORMATION 0x1013 + +/* 4.27 SetAutoCalibrationMode WBF00004073 */ +#define WSM_MIB_ID_SET_AUTO_CALIBRATION_MODE 0x1015 + +/* 4.28 SetTxRateRetryPolicy */ +#define WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY 0x1016 + +/* 4.29 SetHostMessageTypeFilter */ +#define WSM_MIB_ID_SET_HOST_MSG_TYPE_FILTER 0x1017 + +/* 4.30 P2PFindInfo */ +#define WSM_MIB_ID_P2P_FIND_INFO 0x1018 + +/* 4.31 P2PPsModeInfo */ +#define WSM_MIB_ID_P2P_PS_MODE_INFO 0x1019 + +/* 4.32 SetEtherTypeDataFrameFilter */ +#define WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER 0x101A + +/* 4.33 SetUDPPortDataFrameFilter */ +#define WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER 0x101B + +/* 4.34 SetMagicDataFrameFilter */ +#define WSM_MIB_ID_SET_MAGIC_DATAFRAME_FILTER 0x101C + +/* 4.35 P2PDeviceInfo */ +#define WSM_MIB_ID_P2P_DEVICE_INFO 0x101D + +/* 4.36 SetWCDMABand */ +#define WSM_MIB_ID_SET_WCDMA_BAND 0x101E + +/* 4.37 GroupTxSequenceCounter */ +#define WSM_MIB_ID_GRP_SEQ_COUNTER 0x101F + +/* 4.38 ProtectedMgmtPolicy */ +#define WSM_MIB_ID_PROTECTED_MGMT_POLICY 0x1020 + +/* 4.39 SetHtProtection */ +#define WSM_MIB_ID_SET_HT_PROTECTION 0x1021 + +/* 4.40 GPIO Command */ +#define WSM_MIB_ID_GPIO_COMMAND 0x1022 + +/* 4.41 TSF Counter Value */ +#define WSM_MIB_ID_TSF_COUNTER 0x1023 + +/* Test Purposes Only */ +#define WSM_MIB_ID_BLOCK_ACK_INFO 0x100D + +/* 4.42 UseMultiTxConfMessage */ +#define WSM_MIB_USE_MULTI_TX_CONF 0x1024 + +/* 4.43 Keep-alive period */ +#define WSM_MIB_ID_KEEP_ALIVE_PERIOD 0x1025 + +/* 4.44 Disable BSSID filter */ +#define WSM_MIB_ID_DISABLE_BSSID_FILTER 0x1026 + +/* Frame template types */ +#define WSM_FRAME_TYPE_PROBE_REQUEST (0) +#define WSM_FRAME_TYPE_BEACON (1) +#define WSM_FRAME_TYPE_NULL (2) +#define WSM_FRAME_TYPE_QOS_NULL (3) +#define WSM_FRAME_TYPE_PS_POLL (4) +#define WSM_FRAME_TYPE_PROBE_RESPONSE (5) + +#define WSM_FRAME_GREENFIELD (0x80) /* See 4.11 */ + +/* Status */ +/* The WSM firmware has completed a request */ +/* successfully. */ +#define WSM_STATUS_SUCCESS (0) + +/* This is a generic failure code if other error codes do */ +/* not apply. */ +#define WSM_STATUS_FAILURE (1) + +/* A request contains one or more invalid parameters. */ +#define WSM_INVALID_PARAMETER (2) + +/* The request cannot perform because the device is in */ +/* an inappropriate mode. */ +#define WSM_ACCESS_DENIED (3) + +/* The frame received includes a decryption error. */ +#define WSM_STATUS_DECRYPTFAILURE (4) + +/* A MIC failure is detected in the received packets. */ +#define WSM_STATUS_MICFAILURE (5) + +/* The transmit request failed due to retry limit being */ +/* exceeded. */ +#define WSM_STATUS_RETRY_EXCEEDED (6) + +/* The transmit request failed due to MSDU life time */ +/* being exceeded. */ +#define WSM_STATUS_TX_LIFETIME_EXCEEDED (7) + +/* The link to the AP is lost. */ +#define WSM_STATUS_LINK_LOST (8) + +/* No key was found for the encrypted frame */ +#define WSM_STATUS_NO_KEY_FOUND (9) + +/* Jammer was detected when transmitting this frame */ +#define WSM_STATUS_JAMMER_DETECTED (10) + +/* The message should be requeued later. */ +/* This is applicable only to Transmit */ +#define WSM_REQUEUE (11) + +/* Advanced filtering options */ +#define WSM_MAX_FILTER_ELEMENTS (4) + +#define WSM_FILTER_ACTION_IGNORE (0) +#define WSM_FILTER_ACTION_FILTER_IN (1) +#define WSM_FILTER_ACTION_FILTER_OUT (2) + +#define WSM_FILTER_PORT_TYPE_DST (0) +#define WSM_FILTER_PORT_TYPE_SRC (1) + +/* Actual header of WSM messages */ +struct wsm_hdr { + __le16 len; + __le16 id; +}; + +#define WSM_TX_SEQ_MAX (7) +#define WSM_TX_SEQ(seq) \ + ((seq & WSM_TX_SEQ_MAX) << 13) +#define WSM_TX_LINK_ID_MAX (0x0F) +#define WSM_TX_LINK_ID(link_id) \ + ((link_id & WSM_TX_LINK_ID_MAX) << 6) + +#define MAX_BEACON_SKIP_TIME_MS 1000 + +#define WSM_CMD_LAST_CHANCE_TIMEOUT (HZ * 3 / 2) + +/* ******************************************************************** */ +/* WSM capability */ + +#define WSM_STARTUP_IND_ID 0x0801 + +struct wsm_startup_ind { + u16 input_buffers; + u16 input_buffer_size; + u16 status; + u16 hw_id; + u16 hw_subid; + u16 fw_cap; + u16 fw_type; + u16 fw_api; + u16 fw_build; + u16 fw_ver; + char fw_label[128]; + u32 config[4]; +}; + +/* ******************************************************************** */ +/* WSM commands */ + +/* 3.1 */ +#define WSM_CONFIGURATION_REQ_ID 0x0009 +#define WSM_CONFIGURATION_RESP_ID 0x0409 + +struct wsm_tx_power_range { + int min_power_level; + int max_power_level; + u32 stepping; +}; + +struct wsm_configuration { + /* [in] */ u32 dot11MaxTransmitMsduLifeTime; + /* [in] */ u32 dot11MaxReceiveLifeTime; + /* [in] */ u32 dot11RtsThreshold; + /* [in, out] */ u8 *dot11StationId; + /* [in] */ const void *dpdData; + /* [in] */ size_t dpdData_size; + /* [out] */ u8 dot11FrequencyBandsSupported; + /* [out] */ u32 supportedRateMask; + /* [out] */ struct wsm_tx_power_range txPowerRange[2]; +}; + +int wsm_configuration(struct cw1200_common *priv, + struct wsm_configuration *arg); + +/* 3.3 */ +#define WSM_RESET_REQ_ID 0x000A +#define WSM_RESET_RESP_ID 0x040A +struct wsm_reset { + /* [in] */ int link_id; + /* [in] */ bool reset_statistics; +}; + +int wsm_reset(struct cw1200_common *priv, const struct wsm_reset *arg); + +/* 3.5 */ +#define WSM_READ_MIB_REQ_ID 0x0005 +#define WSM_READ_MIB_RESP_ID 0x0405 +int wsm_read_mib(struct cw1200_common *priv, u16 mib_id, void *buf, + size_t buf_size); + +/* 3.7 */ +#define WSM_WRITE_MIB_REQ_ID 0x0006 +#define WSM_WRITE_MIB_RESP_ID 0x0406 +int wsm_write_mib(struct cw1200_common *priv, u16 mib_id, void *buf, + size_t buf_size); + +/* 3.9 */ +#define WSM_START_SCAN_REQ_ID 0x0007 +#define WSM_START_SCAN_RESP_ID 0x0407 + +struct wsm_ssid { + u8 ssid[32]; + u32 length; +}; + +struct wsm_scan_ch { + u16 number; + u32 min_chan_time; + u32 max_chan_time; + u32 tx_power_level; +}; + +struct wsm_scan { + /* WSM_PHY_BAND_... */ + u8 band; + + /* WSM_SCAN_TYPE_... */ + u8 type; + + /* WSM_SCAN_FLAG_... */ + u8 flags; + + /* WSM_TRANSMIT_RATE_... */ + u8 max_tx_rate; + + /* Interval period in TUs that the device shall the re- */ + /* execute the requested scan. Max value supported by the device */ + /* is 256s. */ + u32 auto_scan_interval; + + /* Number of probe requests (per SSID) sent to one (1) */ + /* channel. Zero (0) means that none is send, which */ + /* means that a passive scan is to be done. Value */ + /* greater than zero (0) means that an active scan is to */ + /* be done. */ + u32 num_probes; + + /* Number of channels to be scanned. */ + /* Maximum value is WSM_SCAN_MAX_NUM_OF_CHANNELS. */ + u8 num_channels; + + /* Number of SSID provided in the scan command (this */ + /* is zero (0) in broadcast scan) */ + /* The maximum number of SSIDs is WSM_SCAN_MAX_NUM_OF_SSIDS. */ + u8 num_ssids; + + /* The delay time (in microseconds) period */ + /* before sending a probe-request. */ + u8 probe_delay; + + /* SSIDs to be scanned [numOfSSIDs]; */ + struct wsm_ssid *ssids; + + /* Channels to be scanned [numOfChannels]; */ + struct wsm_scan_ch *ch; +}; + +int wsm_scan(struct cw1200_common *priv, const struct wsm_scan *arg); + +/* 3.11 */ +#define WSM_STOP_SCAN_REQ_ID 0x0008 +#define WSM_STOP_SCAN_RESP_ID 0x0408 +int wsm_stop_scan(struct cw1200_common *priv); + +/* 3.13 */ +#define WSM_SCAN_COMPLETE_IND_ID 0x0806 +struct wsm_scan_complete { + /* WSM_STATUS_... */ + u32 status; + + /* WSM_PSM_... */ + u8 psm; + + /* Number of channels that the scan operation completed. */ + u8 num_channels; +}; + +/* 3.14 */ +#define WSM_TX_CONFIRM_IND_ID 0x0404 +#define WSM_MULTI_TX_CONFIRM_ID 0x041E + +struct wsm_tx_confirm { + /* Packet identifier used in wsm_tx. */ + u32 packet_id; + + /* WSM_STATUS_... */ + u32 status; + + /* WSM_TRANSMIT_RATE_... */ + u8 tx_rate; + + /* The number of times the frame was transmitted */ + /* without receiving an acknowledgement. */ + u8 ack_failures; + + /* WSM_TX_STATUS_... */ + u16 flags; + + /* The total time in microseconds that the frame spent in */ + /* the WLAN device before transmission as completed. */ + u32 media_delay; + + /* The total time in microseconds that the frame spent in */ + /* the WLAN device before transmission was started. */ + u32 tx_queue_delay; +}; + +/* 3.15 */ +typedef void (*wsm_tx_confirm_cb) (struct cw1200_common *priv, + struct wsm_tx_confirm *arg); + +/* Note that ideology of wsm_tx struct is different against the rest of + * WSM API. wsm_hdr is /not/ a caller-adapted struct to be used as an input + * argument for WSM call, but a prepared bytestream to be sent to firmware. + * It is filled partly in cw1200_tx, partly in low-level WSM code. + * Please pay attention once again: ideology is different. + * + * Legend: + * - [in]: cw1200_tx must fill this field. + * - [wsm]: the field is filled by low-level WSM. + */ +struct wsm_tx { + /* common WSM header */ + struct wsm_hdr hdr; + + /* Packet identifier that meant to be used in completion. */ + u32 packet_id; /* Note this is actually a cookie */ + + /* WSM_TRANSMIT_RATE_... */ + u8 max_tx_rate; + + /* WSM_QUEUE_... */ + u8 queue_id; + + /* True: another packet is pending on the host for transmission. */ + u8 more; + + /* Bit 0 = 0 - Start expiry time from first Tx attempt (default) */ + /* Bit 0 = 1 - Start expiry time from receipt of Tx Request */ + /* Bits 3:1 - PTA Priority */ + /* Bits 6:4 - Tx Rate Retry Policy */ + /* Bit 7 - Reserved */ + u8 flags; + + /* Should be 0. */ + u32 reserved; + + /* The elapsed time in TUs, after the initial transmission */ + /* of an MSDU, after which further attempts to transmit */ + /* the MSDU shall be terminated. Overrides the global */ + /* dot11MaxTransmitMsduLifeTime setting [optional] */ + /* Device will set the default value if this is 0. */ + __le32 expire_time; + + /* WSM_HT_TX_... */ + __le32 ht_tx_parameters; +} __packed; + +/* = sizeof(generic hi hdr) + sizeof(wsm hdr) + sizeof(alignment) */ +#define WSM_TX_EXTRA_HEADROOM (28) + +/* 3.16 */ +#define WSM_RECEIVE_IND_ID 0x0804 + +struct wsm_rx { + /* WSM_STATUS_... */ + u32 status; + + /* Specifies the channel of the received packet. */ + u16 channel_number; + + /* WSM_TRANSMIT_RATE_... */ + u8 rx_rate; + + /* This value is expressed in signed Q8.0 format for */ + /* RSSI and unsigned Q7.1 format for RCPI. */ + u8 rcpi_rssi; + + /* WSM_RX_STATUS_... */ + u32 flags; +}; + +/* = sizeof(generic hi hdr) + sizeof(wsm hdr) */ +#define WSM_RX_EXTRA_HEADROOM (16) + +typedef void (*wsm_rx_cb) (struct cw1200_common *priv, struct wsm_rx *arg, + struct sk_buff **skb_p); + +/* 3.17 */ +struct wsm_event { + /* WSM_STATUS_... */ + /* [out] */ u32 id; + + /* Indication parameters. */ + /* For error indication, this shall be a 32-bit WSM status. */ + /* For RCPI or RSSI indication, this should be an 8-bit */ + /* RCPI or RSSI value. */ + /* [out] */ u32 data; +}; + +struct cw1200_wsm_event { + struct list_head link; + struct wsm_event evt; +}; + +/* 3.18 - 3.22 */ +/* Measurement. Skipped for now. Irrelevent. */ + +typedef void (*wsm_event_cb) (struct cw1200_common *priv, + struct wsm_event *arg); + +/* 3.23 */ +#define WSM_JOIN_REQ_ID 0x000B +#define WSM_JOIN_RESP_ID 0x040B + +struct wsm_join { + /* WSM_JOIN_MODE_... */ + u8 mode; + + /* WSM_PHY_BAND_... */ + u8 band; + + /* Specifies the channel number to join. The channel */ + /* number will be mapped to an actual frequency */ + /* according to the band */ + u16 channel_number; + + /* Specifies the BSSID of the BSS or IBSS to be joined */ + /* or the IBSS to be started. */ + u8 bssid[6]; + + /* ATIM window of IBSS */ + /* When ATIM window is zero the initiated IBSS does */ + /* not support power saving. */ + u16 atim_window; + + /* WSM_JOIN_PREAMBLE_... */ + u8 preamble_type; + + /* Specifies if a probe request should be send with the */ + /* specified SSID when joining to the network. */ + u8 probe_for_join; + + /* DTIM Period (In multiples of beacon interval) */ + u8 dtim_period; + + /* WSM_JOIN_FLAGS_... */ + u8 flags; + + /* Length of the SSID */ + u32 ssid_len; + + /* Specifies the SSID of the IBSS to join or start */ + u8 ssid[32]; + + /* Specifies the time between TBTTs in TUs */ + u32 beacon_interval; + + /* A bit mask that defines the BSS basic rate set. */ + u32 basic_rate_set; +}; + +struct wsm_join_cnf { + u32 status; + + /* Minimum transmission power level in units of 0.1dBm */ + u32 min_power_level; + + /* Maximum transmission power level in units of 0.1dBm */ + u32 max_power_level; +}; + +int wsm_join(struct cw1200_common *priv, struct wsm_join *arg); + +/* 3.24 */ +struct wsm_join_complete { + /* WSM_STATUS_... */ + u32 status; +}; + +/* 3.25 */ +#define WSM_SET_PM_REQ_ID 0x0010 +#define WSM_SET_PM_RESP_ID 0x0410 +struct wsm_set_pm { + /* WSM_PSM_... */ + u8 mode; + + /* in unit of 500us; 0 to use default */ + u8 fast_psm_idle_period; + + /* in unit of 500us; 0 to use default */ + u8 ap_psm_change_period; + + /* in unit of 500us; 0 to disable auto-pspoll */ + u8 min_auto_pspoll_period; +}; + +int wsm_set_pm(struct cw1200_common *priv, const struct wsm_set_pm *arg); + +/* 3.27 */ +struct wsm_set_pm_complete { + u8 psm; /* WSM_PSM_... */ +}; + +/* 3.28 */ +#define WSM_SET_BSS_PARAMS_REQ_ID 0x0011 +#define WSM_SET_BSS_PARAMS_RESP_ID 0x0411 +struct wsm_set_bss_params { + /* This resets the beacon loss counters only */ + u8 reset_beacon_loss; + + /* The number of lost consecutive beacons after which */ + /* the WLAN device should indicate the BSS-Lost event */ + /* to the WLAN host driver. */ + u8 beacon_lost_count; + + /* The AID received during the association process. */ + u16 aid; + + /* The operational rate set mask */ + u32 operational_rate_set; +}; + +int wsm_set_bss_params(struct cw1200_common *priv, + const struct wsm_set_bss_params *arg); + +/* 3.30 */ +#define WSM_ADD_KEY_REQ_ID 0x000C +#define WSM_ADD_KEY_RESP_ID 0x040C +struct wsm_add_key { + u8 type; /* WSM_KEY_TYPE_... */ + u8 index; /* Key entry index: 0 -- WSM_KEY_MAX_INDEX */ + u16 reserved; + union { + struct { + u8 peer[6]; /* MAC address of the peer station */ + u8 reserved; + u8 keylen; /* Key length in bytes */ + u8 keydata[16]; /* Key data */ + } __packed wep_pairwise; + struct { + u8 keyid; /* Unique per key identifier (0..3) */ + u8 keylen; /* Key length in bytes */ + u16 reserved; + u8 keydata[16]; /* Key data */ + } __packed wep_group; + struct { + u8 peer[6]; /* MAC address of the peer station */ + u16 reserved; + u8 keydata[16]; /* TKIP key data */ + u8 rx_mic_key[8]; /* Rx MIC key */ + u8 tx_mic_key[8]; /* Tx MIC key */ + } __packed tkip_pairwise; + struct { + u8 keydata[16]; /* TKIP key data */ + u8 rx_mic_key[8]; /* Rx MIC key */ + u8 keyid; /* Key ID */ + u8 reserved[3]; + u8 rx_seqnum[8]; /* Receive Sequence Counter */ + } __packed tkip_group; + struct { + u8 peer[6]; /* MAC address of the peer station */ + u16 reserved; + u8 keydata[16]; /* AES key data */ + } __packed aes_pairwise; + struct { + u8 keydata[16]; /* AES key data */ + u8 keyid; /* Key ID */ + u8 reserved[3]; + u8 rx_seqnum[8]; /* Receive Sequence Counter */ + } __packed aes_group; + struct { + u8 peer[6]; /* MAC address of the peer station */ + u8 keyid; /* Key ID */ + u8 reserved; + u8 keydata[16]; /* WAPI key data */ + u8 mic_key[16]; /* MIC key data */ + } __packed wapi_pairwise; + struct { + u8 keydata[16]; /* WAPI key data */ + u8 mic_key[16]; /* MIC key data */ + u8 keyid; /* Key ID */ + u8 reserved[3]; + } __packed wapi_group; + } __packed; +} __packed; + +int wsm_add_key(struct cw1200_common *priv, const struct wsm_add_key *arg); + +/* 3.32 */ +#define WSM_REMOVE_KEY_REQ_ID 0x000D +#define WSM_REMOVE_KEY_RESP_ID 0x040D +struct wsm_remove_key { + u8 index; /* Key entry index : 0-10 */ +}; + +int wsm_remove_key(struct cw1200_common *priv, + const struct wsm_remove_key *arg); + +/* 3.34 */ +struct wsm_set_tx_queue_params { + /* WSM_ACK_POLICY_... */ + u8 ackPolicy; + + /* Medium Time of TSPEC (in 32us units) allowed per */ + /* One Second Averaging Period for this queue. */ + u16 allowedMediumTime; + + /* dot11MaxTransmitMsduLifetime to be used for the */ + /* specified queue. */ + u32 maxTransmitLifetime; +}; + +struct wsm_tx_queue_params { + /* NOTE: index is a linux queue id. */ + struct wsm_set_tx_queue_params params[4]; +}; + + +#define WSM_TX_QUEUE_SET(queue_params, queue, ack_policy, allowed_time,\ + max_life_time) \ +do { \ + struct wsm_set_tx_queue_params *p = &(queue_params)->params[queue]; \ + p->ackPolicy = (ack_policy); \ + p->allowedMediumTime = (allowed_time); \ + p->maxTransmitLifetime = (max_life_time); \ +} while (0) + +int wsm_set_tx_queue_params(struct cw1200_common *priv, + const struct wsm_set_tx_queue_params *arg, u8 id); + +/* 3.36 */ +#define WSM_EDCA_PARAMS_REQ_ID 0x0013 +#define WSM_EDCA_PARAMS_RESP_ID 0x0413 +struct wsm_edca_queue_params { + /* CWmin (in slots) for the access class. */ + u16 cwmin; + + /* CWmax (in slots) for the access class. */ + u16 cwmax; + + /* AIFS (in slots) for the access class. */ + u16 aifns; + + /* TX OP Limit (in microseconds) for the access class. */ + u16 txop_limit; + + /* dot11MaxReceiveLifetime to be used for the specified */ + /* the access class. Overrides the global */ + /* dot11MaxReceiveLifetime value */ + u32 max_rx_lifetime; +}; + +struct wsm_edca_params { + /* NOTE: index is a linux queue id. */ + struct wsm_edca_queue_params params[4]; + bool uapsd_enable[4]; +}; + +#define TXOP_UNIT 32 +#define WSM_EDCA_SET(__edca, __queue, __aifs, __cw_min, __cw_max, __txop, __lifetime,\ + __uapsd) \ + do { \ + struct wsm_edca_queue_params *p = &(__edca)->params[__queue]; \ + p->cwmin = __cw_min; \ + p->cwmax = __cw_max; \ + p->aifns = __aifs; \ + p->txop_limit = ((__txop) * TXOP_UNIT); \ + p->max_rx_lifetime = __lifetime; \ + (__edca)->uapsd_enable[__queue] = (__uapsd); \ + } while (0) + +int wsm_set_edca_params(struct cw1200_common *priv, + const struct wsm_edca_params *arg); + +int wsm_set_uapsd_param(struct cw1200_common *priv, + const struct wsm_edca_params *arg); + +/* 3.38 */ +/* Set-System info. Skipped for now. Irrelevent. */ + +/* 3.40 */ +#define WSM_SWITCH_CHANNEL_REQ_ID 0x0016 +#define WSM_SWITCH_CHANNEL_RESP_ID 0x0416 + +struct wsm_switch_channel { + /* 1 - means the STA shall not transmit any further */ + /* frames until the channel switch has completed */ + u8 mode; + + /* Number of TBTTs until channel switch occurs. */ + /* 0 - indicates switch shall occur at any time */ + /* 1 - occurs immediately before the next TBTT */ + u8 switch_count; + + /* The new channel number to switch to. */ + /* Note this is defined as per section 2.7. */ + u16 channel_number; +}; + +int wsm_switch_channel(struct cw1200_common *priv, + const struct wsm_switch_channel *arg); + +typedef void (*wsm_channel_switch_cb) (struct cw1200_common *priv); + +#define WSM_START_REQ_ID 0x0017 +#define WSM_START_RESP_ID 0x0417 + +struct wsm_start { + /* WSM_START_MODE_... */ + /* [in] */ u8 mode; + + /* WSM_PHY_BAND_... */ + /* [in] */ u8 band; + + /* Channel number */ + /* [in] */ u16 channel_number; + + /* Client Traffic window in units of TU */ + /* Valid only when mode == ..._P2P */ + /* [in] */ u32 ct_window; + + /* Interval between two consecutive */ + /* beacon transmissions in TU. */ + /* [in] */ u32 beacon_interval; + + /* DTIM period in terms of beacon intervals */ + /* [in] */ u8 dtim_period; + + /* WSM_JOIN_PREAMBLE_... */ + /* [in] */ u8 preamble; + + /* The delay time (in microseconds) period */ + /* before sending a probe-request. */ + /* [in] */ u8 probe_delay; + + /* Length of the SSID */ + /* [in] */ u8 ssid_len; + + /* SSID of the BSS or P2P_GO to be started now. */ + /* [in] */ u8 ssid[32]; + + /* The basic supported rates for the MiniAP. */ + /* [in] */ u32 basic_rate_set; +}; + +int wsm_start(struct cw1200_common *priv, const struct wsm_start *arg); + +#define WSM_BEACON_TRANSMIT_REQ_ID 0x0018 +#define WSM_BEACON_TRANSMIT_RESP_ID 0x0418 + +struct wsm_beacon_transmit { + /* 1: enable; 0: disable */ + /* [in] */ u8 enable_beaconing; +}; + +int wsm_beacon_transmit(struct cw1200_common *priv, + const struct wsm_beacon_transmit *arg); + +int wsm_start_find(struct cw1200_common *priv); + +int wsm_stop_find(struct cw1200_common *priv); + +typedef void (*wsm_find_complete_cb) (struct cw1200_common *priv, u32 status); + +struct wsm_suspend_resume { + /* See 3.52 */ + /* Link ID */ + /* [out] */ int link_id; + /* Stop sending further Tx requests down to device for this link */ + /* [out] */ bool stop; + /* Transmit multicast Frames */ + /* [out] */ bool multicast; + /* The AC on which Tx to be suspended /resumed. */ + /* This is applicable only for U-APSD */ + /* WSM_QUEUE_... */ + /* [out] */ int queue; +}; + +typedef void (*wsm_suspend_resume_cb) (struct cw1200_common *priv, + struct wsm_suspend_resume *arg); + +/* 3.54 Update-IE request. */ +struct wsm_update_ie { + /* WSM_UPDATE_IE_... */ + /* [in] */ u16 what; + /* [in] */ u16 count; + /* [in] */ u8 *ies; + /* [in] */ size_t length; +}; + +int wsm_update_ie(struct cw1200_common *priv, + const struct wsm_update_ie *arg); + +/* 3.56 */ +struct wsm_map_link { + /* MAC address of the remote device */ + /* [in] */ u8 mac_addr[6]; + /* [in] */ u8 link_id; +}; + +int wsm_map_link(struct cw1200_common *priv, const struct wsm_map_link *arg); + +/* ******************************************************************** */ +/* MIB shortcats */ + +static inline int wsm_set_output_power(struct cw1200_common *priv, + int power_level) +{ + __le32 val = __cpu_to_le32(power_level); + return wsm_write_mib(priv, WSM_MIB_ID_DOT11_CURRENT_TX_POWER_LEVEL, + &val, sizeof(val)); +} + +static inline int wsm_set_beacon_wakeup_period(struct cw1200_common *priv, + unsigned dtim_interval, + unsigned listen_interval) +{ + struct { + u8 numBeaconPeriods; + u8 reserved; + __le16 listenInterval; + } val = { + dtim_interval, 0, __cpu_to_le16(listen_interval) + }; + + if (dtim_interval > 0xFF || listen_interval > 0xFFFF) + return -EINVAL; + else + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_WAKEUP_PERIOD, + &val, sizeof(val)); +} + +struct wsm_rcpi_rssi_threshold { + u8 rssiRcpiMode; /* WSM_RCPI_RSSI_... */ + u8 lowerThreshold; + u8 upperThreshold; + u8 rollingAverageCount; +}; + +static inline int wsm_set_rcpi_rssi_threshold(struct cw1200_common *priv, + struct wsm_rcpi_rssi_threshold *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_RCPI_RSSI_THRESHOLD, arg, + sizeof(*arg)); +} + +struct wsm_mib_counters_table { + __le32 plcp_errors; + __le32 fcs_errors; + __le32 tx_packets; + __le32 rx_packets; + __le32 rx_packet_errors; + __le32 rx_decryption_failures; + __le32 rx_mic_failures; + __le32 rx_no_key_failures; + __le32 tx_multicast_frames; + __le32 tx_frames_success; + __le32 tx_frame_failures; + __le32 tx_frames_retried; + __le32 tx_frames_multi_retried; + __le32 rx_frame_duplicates; + __le32 rts_success; + __le32 rts_failures; + __le32 ack_failures; + __le32 rx_multicast_frames; + __le32 rx_frames_success; + __le32 rx_cmac_icv_errors; + __le32 rx_cmac_replays; + __le32 rx_mgmt_ccmp_replays; +} __packed; + +static inline int wsm_get_counters_table(struct cw1200_common *priv, + struct wsm_mib_counters_table *arg) +{ + return wsm_read_mib(priv, WSM_MIB_ID_COUNTERS_TABLE, + arg, sizeof(*arg)); +} + +static inline int wsm_get_station_id(struct cw1200_common *priv, u8 *mac) +{ + return wsm_read_mib(priv, WSM_MIB_ID_DOT11_STATION_ID, mac, ETH_ALEN); +} + +struct wsm_rx_filter { + bool promiscuous; + bool bssid; + bool fcs; + bool probeResponder; +}; + +static inline int wsm_set_rx_filter(struct cw1200_common *priv, + const struct wsm_rx_filter *arg) +{ + __le32 val = 0; + if (arg->promiscuous) + val |= __cpu_to_le32(BIT(0)); + if (arg->bssid) + val |= __cpu_to_le32(BIT(1)); + if (arg->fcs) + val |= __cpu_to_le32(BIT(2)); + if (arg->probeResponder) + val |= __cpu_to_le32(BIT(3)); + return wsm_write_mib(priv, WSM_MIB_ID_RX_FILTER, &val, sizeof(val)); +} + +int wsm_set_probe_responder(struct cw1200_common *priv, bool enable); + +#define WSM_BEACON_FILTER_IE_HAS_CHANGED BIT(0) +#define WSM_BEACON_FILTER_IE_NO_LONGER_PRESENT BIT(1) +#define WSM_BEACON_FILTER_IE_HAS_APPEARED BIT(2) + +struct wsm_beacon_filter_table_entry { + u8 ie_id; + u8 flags; + u8 oui[3]; + u8 match_data[3]; +} __packed; + +struct wsm_mib_beacon_filter_table { + __le32 num; + struct wsm_beacon_filter_table_entry entry[10]; +} __packed; + +static inline int wsm_set_beacon_filter_table(struct cw1200_common *priv, + struct wsm_mib_beacon_filter_table *ft) +{ + size_t size = __le32_to_cpu(ft->num) * + sizeof(struct wsm_beacon_filter_table_entry) + + sizeof(__le32); + + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_TABLE, ft, size); +} + +#define WSM_BEACON_FILTER_ENABLE BIT(0) /* Enable/disable beacon filtering */ +#define WSM_BEACON_FILTER_AUTO_ERP BIT(1) /* If 1 FW will handle ERP IE changes internally */ + +struct wsm_beacon_filter_control { + int enabled; + int bcn_count; +}; + +static inline int wsm_beacon_filter_control(struct cw1200_common *priv, + struct wsm_beacon_filter_control *arg) +{ + struct { + __le32 enabled; + __le32 bcn_count; + } val; + val.enabled = __cpu_to_le32(arg->enabled); + val.bcn_count = __cpu_to_le32(arg->bcn_count); + return wsm_write_mib(priv, WSM_MIB_ID_BEACON_FILTER_ENABLE, &val, + sizeof(val)); +} + +enum wsm_power_mode { + wsm_power_mode_active = 0, + wsm_power_mode_doze = 1, + wsm_power_mode_quiescent = 2, +}; + +struct wsm_operational_mode { + enum wsm_power_mode power_mode; + int disable_more_flag_usage; + int perform_ant_diversity; +}; + +static inline int wsm_set_operational_mode(struct cw1200_common *priv, + const struct wsm_operational_mode *arg) +{ + u8 val = arg->power_mode; + if (arg->disable_more_flag_usage) + val |= BIT(4); + if (arg->perform_ant_diversity) + val |= BIT(5); + return wsm_write_mib(priv, WSM_MIB_ID_OPERATIONAL_POWER_MODE, &val, + sizeof(val)); +} + +struct wsm_template_frame { + u8 frame_type; + u8 rate; + struct sk_buff *skb; +}; + +static inline int wsm_set_template_frame(struct cw1200_common *priv, + struct wsm_template_frame *arg) +{ + int ret; + u8 *p = skb_push(arg->skb, 4); + p[0] = arg->frame_type; + p[1] = arg->rate; + ((__le16 *)p)[1] = __cpu_to_le16(arg->skb->len - 4); + ret = wsm_write_mib(priv, WSM_MIB_ID_TEMPLATE_FRAME, p, arg->skb->len); + skb_pull(arg->skb, 4); + return ret; +} + + +struct wsm_protected_mgmt_policy { + bool protectedMgmtEnable; + bool unprotectedMgmtFramesAllowed; + bool encryptionForAuthFrame; +}; + +static inline int wsm_set_protected_mgmt_policy(struct cw1200_common *priv, + struct wsm_protected_mgmt_policy *arg) +{ + __le32 val = 0; + int ret; + if (arg->protectedMgmtEnable) + val |= __cpu_to_le32(BIT(0)); + if (arg->unprotectedMgmtFramesAllowed) + val |= __cpu_to_le32(BIT(1)); + if (arg->encryptionForAuthFrame) + val |= __cpu_to_le32(BIT(2)); + ret = wsm_write_mib(priv, WSM_MIB_ID_PROTECTED_MGMT_POLICY, + &val, sizeof(val)); + return ret; +} + +struct wsm_mib_block_ack_policy { + u8 tx_tid; + u8 reserved1; + u8 rx_tid; + u8 reserved2; +} __packed; + +static inline int wsm_set_block_ack_policy(struct cw1200_common *priv, + u8 tx_tid_policy, + u8 rx_tid_policy) +{ + struct wsm_mib_block_ack_policy val = { + .tx_tid = tx_tid_policy, + .rx_tid = rx_tid_policy, + }; + return wsm_write_mib(priv, WSM_MIB_ID_BLOCK_ACK_POLICY, &val, + sizeof(val)); +} + +struct wsm_mib_association_mode { + u8 flags; /* WSM_ASSOCIATION_MODE_... */ + u8 preamble; /* WSM_JOIN_PREAMBLE_... */ + u8 greenfield; /* 1 for greenfield */ + u8 mpdu_start_spacing; + __le32 basic_rate_set; +} __packed; + +static inline int wsm_set_association_mode(struct cw1200_common *priv, + struct wsm_mib_association_mode *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_SET_ASSOCIATION_MODE, arg, + sizeof(*arg)); +} + +#define WSM_TX_RATE_POLICY_FLAG_TERMINATE_WHEN_FINISHED BIT(2) +#define WSM_TX_RATE_POLICY_FLAG_COUNT_INITIAL_TRANSMIT BIT(3) +struct wsm_tx_rate_retry_policy { + u8 index; + u8 short_retries; + u8 long_retries; + /* BIT(2) - Terminate retries when Tx rate retry policy + * finishes. + * BIT(3) - Count initial frame transmission as part of + * rate retry counting but not as a retry + * attempt + */ + u8 flags; + u8 rate_recoveries; + u8 reserved[3]; + __le32 rate_count_indices[3]; +} __packed; + +struct wsm_set_tx_rate_retry_policy { + u8 num; + u8 reserved[3]; + struct wsm_tx_rate_retry_policy tbl[8]; +} __packed; + +static inline int wsm_set_tx_rate_retry_policy(struct cw1200_common *priv, + struct wsm_set_tx_rate_retry_policy *arg) +{ + size_t size = 4 + arg->num * sizeof(struct wsm_tx_rate_retry_policy); + return wsm_write_mib(priv, WSM_MIB_ID_SET_TX_RATE_RETRY_POLICY, arg, + size); +} + +/* 4.32 SetEtherTypeDataFrameFilter */ +struct wsm_ether_type_filter_hdr { + u8 num; /* Up to WSM_MAX_FILTER_ELEMENTS */ + u8 reserved[3]; +} __packed; + +struct wsm_ether_type_filter { + u8 action; /* WSM_FILTER_ACTION_XXX */ + u8 reserved; + __le16 type; /* Type of ethernet frame */ +} __packed; + +static inline int wsm_set_ether_type_filter(struct cw1200_common *priv, + struct wsm_ether_type_filter_hdr *arg) +{ + size_t size = sizeof(struct wsm_ether_type_filter_hdr) + + arg->num * sizeof(struct wsm_ether_type_filter); + return wsm_write_mib(priv, WSM_MIB_ID_SET_ETHERTYPE_DATAFRAME_FILTER, + arg, size); +} + +/* 4.33 SetUDPPortDataFrameFilter */ +struct wsm_udp_port_filter_hdr { + u8 num; /* Up to WSM_MAX_FILTER_ELEMENTS */ + u8 reserved[3]; +} __packed; + +struct wsm_udp_port_filter { + u8 action; /* WSM_FILTER_ACTION_XXX */ + u8 type; /* WSM_FILTER_PORT_TYPE_XXX */ + __le16 port; /* Port number */ +} __packed; + +static inline int wsm_set_udp_port_filter(struct cw1200_common *priv, + struct wsm_udp_port_filter_hdr *arg) +{ + size_t size = sizeof(struct wsm_udp_port_filter_hdr) + + arg->num * sizeof(struct wsm_udp_port_filter); + return wsm_write_mib(priv, WSM_MIB_ID_SET_UDPPORT_DATAFRAME_FILTER, + arg, size); +} + +/* Undocumented MIBs: */ +/* 4.35 P2PDeviceInfo */ +#define D11_MAX_SSID_LEN (32) + +struct wsm_p2p_device_type { + __le16 category_id; + u8 oui[4]; + __le16 subcategory_id; +} __packed; + +struct wsm_p2p_device_info { + struct wsm_p2p_device_type primaryDevice; + u8 reserved1[3]; + u8 devname_size; + u8 local_devname[D11_MAX_SSID_LEN]; + u8 reserved2[3]; + u8 num_secdev_supported; + struct wsm_p2p_device_type secdevs[0]; +} __packed; + +/* 4.36 SetWCDMABand - WO */ +struct wsm_cdma_band { + u8 wcdma_band; + u8 reserved[3]; +} __packed; + +/* 4.37 GroupTxSequenceCounter - RO */ +struct wsm_group_tx_seq { + __le32 bits_47_16; + __le16 bits_15_00; + __le16 reserved; +} __packed; + +/* 4.39 SetHtProtection - WO */ +#define WSM_DUAL_CTS_PROT_ENB (1 << 0) +#define WSM_NON_GREENFIELD_STA_PRESENT (1 << 1) +#define WSM_HT_PROT_MODE__NO_PROT (0 << 2) +#define WSM_HT_PROT_MODE__NON_MEMBER (1 << 2) +#define WSM_HT_PROT_MODE__20_MHZ (2 << 2) +#define WSM_HT_PROT_MODE__NON_HT_MIXED (3 << 2) +#define WSM_LSIG_TXOP_PROT_FULL (1 << 4) +#define WSM_LARGE_L_LENGTH_PROT (1 << 5) + +struct wsm_ht_protection { + __le32 flags; +} __packed; + +/* 4.40 GPIO Command - R/W */ +#define WSM_GPIO_COMMAND_SETUP 0 +#define WSM_GPIO_COMMAND_READ 1 +#define WSM_GPIO_COMMAND_WRITE 2 +#define WSM_GPIO_COMMAND_RESET 3 +#define WSM_GPIO_ALL_PINS 0xFF + +struct wsm_gpio_command { + u8 command; + u8 pin; + __le16 config; +} __packed; + +/* 4.41 TSFCounter - RO */ +struct wsm_tsf_counter { + __le64 tsf_counter; +} __packed; + +/* 4.43 Keep alive period */ +struct wsm_keep_alive_period { + __le16 period; + u8 reserved[2]; +} __packed; + +static inline int wsm_keep_alive_period(struct cw1200_common *priv, + int period) +{ + struct wsm_keep_alive_period arg = { + .period = __cpu_to_le16(period), + }; + return wsm_write_mib(priv, WSM_MIB_ID_KEEP_ALIVE_PERIOD, + &arg, sizeof(arg)); +}; + +/* BSSID filtering */ +struct wsm_set_bssid_filtering { + u8 filter; + u8 reserved[3]; +} __packed; + +static inline int wsm_set_bssid_filtering(struct cw1200_common *priv, + bool enabled) +{ + struct wsm_set_bssid_filtering arg = { + .filter = !enabled, + }; + return wsm_write_mib(priv, WSM_MIB_ID_DISABLE_BSSID_FILTER, + &arg, sizeof(arg)); +} + +/* Multicast filtering - 4.5 */ +struct wsm_mib_multicast_filter { + __le32 enable; + __le32 num_addrs; + u8 macaddrs[WSM_MAX_GRP_ADDRTABLE_ENTRIES][ETH_ALEN]; +} __packed; + +static inline int wsm_set_multicast_filter(struct cw1200_common *priv, + struct wsm_mib_multicast_filter *fp) +{ + return wsm_write_mib(priv, WSM_MIB_ID_DOT11_GROUP_ADDRESSES_TABLE, + fp, sizeof(*fp)); +} + +/* ARP IPv4 filtering - 4.10 */ +struct wsm_mib_arp_ipv4_filter { + __le32 enable; + __be32 ipv4addrs[WSM_MAX_ARP_IP_ADDRTABLE_ENTRIES]; +} __packed; + +static inline int wsm_set_arp_ipv4_filter(struct cw1200_common *priv, + struct wsm_mib_arp_ipv4_filter *fp) +{ + return wsm_write_mib(priv, WSM_MIB_ID_ARP_IP_ADDRESSES_TABLE, + fp, sizeof(*fp)); +} + +/* P2P Power Save Mode Info - 4.31 */ +struct wsm_p2p_ps_modeinfo { + u8 opp_ps_ct_window; + u8 count; + u8 reserved; + u8 dtim_count; + __le32 duration; + __le32 interval; + __le32 start_time; +} __packed; + +static inline int wsm_set_p2p_ps_modeinfo(struct cw1200_common *priv, + struct wsm_p2p_ps_modeinfo *mi) +{ + return wsm_write_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO, + mi, sizeof(*mi)); +} + +static inline int wsm_get_p2p_ps_modeinfo(struct cw1200_common *priv, + struct wsm_p2p_ps_modeinfo *mi) +{ + return wsm_read_mib(priv, WSM_MIB_ID_P2P_PS_MODE_INFO, + mi, sizeof(*mi)); +} + +/* UseMultiTxConfMessage */ + +static inline int wsm_use_multi_tx_conf(struct cw1200_common *priv, + bool enabled) +{ + __le32 arg = enabled ? __cpu_to_le32(1) : 0; + + return wsm_write_mib(priv, WSM_MIB_USE_MULTI_TX_CONF, + &arg, sizeof(arg)); +} + + +/* 4.26 SetUpasdInformation */ +struct wsm_uapsd_info { + __le16 uapsd_flags; + __le16 min_auto_trigger_interval; + __le16 max_auto_trigger_interval; + __le16 auto_trigger_step; +}; + +static inline int wsm_set_uapsd_info(struct cw1200_common *priv, + struct wsm_uapsd_info *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_SET_UAPSD_INFORMATION, + arg, sizeof(*arg)); +} + +/* 4.22 OverrideInternalTxRate */ +struct wsm_override_internal_txrate { + u8 internalTxRate; + u8 nonErpInternalTxRate; + u8 reserved[2]; +} __packed; + +static inline int wsm_set_override_internal_txrate(struct cw1200_common *priv, + struct wsm_override_internal_txrate *arg) +{ + return wsm_write_mib(priv, WSM_MIB_ID_OVERRIDE_INTERNAL_TX_RATE, + arg, sizeof(*arg)); +} + +/* ******************************************************************** */ +/* WSM TX port control */ + +void wsm_lock_tx(struct cw1200_common *priv); +void wsm_lock_tx_async(struct cw1200_common *priv); +bool wsm_flush_tx(struct cw1200_common *priv); +void wsm_unlock_tx(struct cw1200_common *priv); + +/* ******************************************************************** */ +/* WSM / BH API */ + +int wsm_handle_exception(struct cw1200_common *priv, u8 *data, size_t len); +int wsm_handle_rx(struct cw1200_common *priv, u16 id, struct wsm_hdr *wsm, + struct sk_buff **skb_p); + +/* ******************************************************************** */ +/* wsm_buf API */ + +struct wsm_buf { + u8 *begin; + u8 *data; + u8 *end; +}; + +void wsm_buf_init(struct wsm_buf *buf); +void wsm_buf_deinit(struct wsm_buf *buf); + +/* ******************************************************************** */ +/* wsm_cmd API */ + +struct wsm_cmd { + spinlock_t lock; /* Protect structure from multiple access */ + int done; + u8 *ptr; + size_t len; + void *arg; + int ret; + u16 cmd; +}; + +/* ******************************************************************** */ +/* WSM TX buffer access */ + +int wsm_get_tx(struct cw1200_common *priv, u8 **data, + size_t *tx_len, int *burst); +void wsm_txed(struct cw1200_common *priv, u8 *data); + +/* ******************************************************************** */ +/* Queue mapping: WSM <---> linux */ +/* Linux: VO VI BE BK */ +/* WSM: BE BK VI VO */ + +static inline u8 wsm_queue_id_to_linux(u8 queue_id) +{ + static const u8 queue_mapping[] = { + 2, 3, 1, 0 + }; + return queue_mapping[queue_id]; +} + +static inline u8 wsm_queue_id_to_wsm(u8 queue_id) +{ + static const u8 queue_mapping[] = { + 3, 2, 0, 1 + }; + return queue_mapping[queue_id]; +} + +#endif /* CW1200_HWIO_H_INCLUDED */ diff --git a/drivers/net/wireless/mediatek/Kconfig b/drivers/net/wireless/mediatek/Kconfig new file mode 100644 index 0000000000000..cba300c6b5da5 --- /dev/null +++ b/drivers/net/wireless/mediatek/Kconfig @@ -0,0 +1,10 @@ +menuconfig WL_MEDIATEK + bool "Mediatek Wireless LAN support" + ---help--- + Enable community drivers for MediaTek WiFi devices. + Those drivers make use of the Linux mac80211 stack. + + +if WL_MEDIATEK +source "drivers/net/wireless/mediatek/mt7601u/Kconfig" +endif # WL_MEDIATEK diff --git a/drivers/net/wireless/mediatek/Makefile b/drivers/net/wireless/mediatek/Makefile new file mode 100644 index 0000000000000..9d5f182fd7fdc --- /dev/null +++ b/drivers/net/wireless/mediatek/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MT7601U) += mt7601u/ diff --git a/drivers/net/wireless/mediatek/mt7601u/Kconfig b/drivers/net/wireless/mediatek/mt7601u/Kconfig new file mode 100644 index 0000000000000..f46bed92796b9 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/Kconfig @@ -0,0 +1,6 @@ +config MT7601U + tristate "MediaTek MT7601U (USB) support" + depends on MAC80211 + depends on USB + ---help--- + This adds support for MT7601U-based wireless USB dongles. diff --git a/drivers/net/wireless/mediatek/mt7601u/Makefile b/drivers/net/wireless/mediatek/mt7601u/Makefile new file mode 100644 index 0000000000000..ea9ed8a5db4d3 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/Makefile @@ -0,0 +1,9 @@ +ccflags-y += -D__CHECK_ENDIAN__ + +obj-$(CONFIG_MT7601U) += mt7601u.o + +mt7601u-objs = \ + usb.o init.o main.o mcu.o trace.o dma.o core.o eeprom.o phy.o \ + mac.o util.o debugfs.o tx.o + +CFLAGS_trace.o := -I$(src) diff --git a/drivers/net/wireless/mediatek/mt7601u/core.c b/drivers/net/wireless/mediatek/mt7601u/core.c new file mode 100644 index 0000000000000..0aabd790f985f --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/core.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" + +int mt7601u_wait_asic_ready(struct mt7601u_dev *dev) +{ + int i = 100; + u32 val; + + do { + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return -EIO; + + val = mt7601u_rr(dev, MT_MAC_CSR0); + if (val && ~val) + return 0; + + udelay(10); + } while (i--); + + return -EIO; +} + +bool mt76_poll(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return false; + + cur = mt7601u_rr(dev, offset) & mask; + if (cur == val) + return true; + + udelay(10); + } while (timeout-- > 0); + + dev_err(dev->dev, "Error: Time out with reg %08x\n", offset); + + return false; +} + +bool mt76_poll_msec(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val, + int timeout) +{ + u32 cur; + + timeout /= 10; + do { + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return false; + + cur = mt7601u_rr(dev, offset) & mask; + if (cur == val) + return true; + + msleep(10); + } while (timeout-- > 0); + + dev_err(dev->dev, "Error: Time out with reg %08x\n", offset); + + return false; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/debugfs.c b/drivers/net/wireless/mediatek/mt7601u/debugfs.c new file mode 100644 index 0000000000000..fc008475a03b2 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/debugfs.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "eeprom.h" + +static int +mt76_reg_set(void *data, u64 val) +{ + struct mt7601u_dev *dev = data; + + mt76_wr(dev, dev->debugfs_reg, val); + return 0; +} + +static int +mt76_reg_get(void *data, u64 *val) +{ + struct mt7601u_dev *dev = data; + + *val = mt76_rr(dev, dev->debugfs_reg); + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(fops_regval, mt76_reg_get, mt76_reg_set, "0x%08llx\n"); + +static int +mt7601u_ampdu_stat_read(struct seq_file *file, void *data) +{ + struct mt7601u_dev *dev = file->private; + int i, j; + +#define stat_printf(grp, off, name) \ + seq_printf(file, #name ":\t%llu\n", dev->stats.grp[off]) + + stat_printf(rx_stat, 0, rx_crc_err); + stat_printf(rx_stat, 1, rx_phy_err); + stat_printf(rx_stat, 2, rx_false_cca); + stat_printf(rx_stat, 3, rx_plcp_err); + stat_printf(rx_stat, 4, rx_fifo_overflow); + stat_printf(rx_stat, 5, rx_duplicate); + + stat_printf(tx_stat, 0, tx_fail_cnt); + stat_printf(tx_stat, 1, tx_bcn_cnt); + stat_printf(tx_stat, 2, tx_success); + stat_printf(tx_stat, 3, tx_retransmit); + stat_printf(tx_stat, 4, tx_zero_len); + stat_printf(tx_stat, 5, tx_underflow); + + stat_printf(aggr_stat, 0, non_aggr_tx); + stat_printf(aggr_stat, 1, aggr_tx); + + stat_printf(zero_len_del, 0, tx_zero_len_del); + stat_printf(zero_len_del, 1, rx_zero_len_del); +#undef stat_printf + + seq_puts(file, "Aggregations stats:\n"); + for (i = 0; i < 4; i++) { + for (j = 0; j < 8; j++) + seq_printf(file, "%08llx ", + dev->stats.aggr_n[i * 8 + j]); + seq_putc(file, '\n'); + } + + seq_printf(file, "recent average AMPDU len: %d\n", + atomic_read(&dev->avg_ampdu_len)); + + return 0; +} + +static int +mt7601u_ampdu_stat_open(struct inode *inode, struct file *f) +{ + return single_open(f, mt7601u_ampdu_stat_read, inode->i_private); +} + +static const struct file_operations fops_ampdu_stat = { + .open = mt7601u_ampdu_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int +mt7601u_eeprom_param_read(struct seq_file *file, void *data) +{ + struct mt7601u_dev *dev = file->private; + struct mt7601u_rate_power *rp = &dev->ee->power_rate_table; + struct tssi_data *td = &dev->ee->tssi_data; + int i; + + seq_printf(file, "RF freq offset: %hhx\n", dev->ee->rf_freq_off); + seq_printf(file, "RSSI offset: %hhx %hhx\n", + dev->ee->rssi_offset[0], dev->ee->rssi_offset[1]); + seq_printf(file, "Reference temp: %hhx\n", dev->ee->ref_temp); + seq_printf(file, "LNA gain: %hhx\n", dev->ee->lna_gain); + seq_printf(file, "Reg channels: %hhu-%hhu\n", dev->ee->reg.start, + dev->ee->reg.start + dev->ee->reg.num - 1); + + seq_puts(file, "Per rate power:\n"); + for (i = 0; i < 2; i++) + seq_printf(file, "\t raw:%02hhx bw20:%02hhx bw40:%02hhx\n", + rp->cck[i].raw, rp->cck[i].bw20, rp->cck[i].bw40); + for (i = 0; i < 4; i++) + seq_printf(file, "\t raw:%02hhx bw20:%02hhx bw40:%02hhx\n", + rp->ofdm[i].raw, rp->ofdm[i].bw20, rp->ofdm[i].bw40); + for (i = 0; i < 4; i++) + seq_printf(file, "\t raw:%02hhx bw20:%02hhx bw40:%02hhx\n", + rp->ht[i].raw, rp->ht[i].bw20, rp->ht[i].bw40); + + seq_puts(file, "Per channel power:\n"); + for (i = 0; i < 7; i++) + seq_printf(file, "\t tx_power ch%u:%02hhx ch%u:%02hhx\n", + i * 2 + 1, dev->ee->chan_pwr[i * 2], + i * 2 + 2, dev->ee->chan_pwr[i * 2 + 1]); + + if (!dev->ee->tssi_enabled) + return 0; + + seq_puts(file, "TSSI:\n"); + seq_printf(file, "\t slope:%02hhx\n", td->slope); + seq_printf(file, "\t offset=%02hhx %02hhx %02hhx\n", + td->offset[0], td->offset[1], td->offset[2]); + seq_printf(file, "\t delta_off:%08x\n", td->tx0_delta_offset); + + return 0; +} + +static int +mt7601u_eeprom_param_open(struct inode *inode, struct file *f) +{ + return single_open(f, mt7601u_eeprom_param_read, inode->i_private); +} + +static const struct file_operations fops_eeprom_param = { + .open = mt7601u_eeprom_param_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void mt7601u_init_debugfs(struct mt7601u_dev *dev) +{ + struct dentry *dir; + + dir = debugfs_create_dir("mt7601u", dev->hw->wiphy->debugfsdir); + if (!dir) + return; + + debugfs_create_u8("temperature", S_IRUSR, dir, &dev->raw_temp); + debugfs_create_u32("temp_mode", S_IRUSR, dir, &dev->temp_mode); + + debugfs_create_u32("regidx", S_IRUSR | S_IWUSR, dir, &dev->debugfs_reg); + debugfs_create_file("regval", S_IRUSR | S_IWUSR, dir, dev, + &fops_regval); + debugfs_create_file("ampdu_stat", S_IRUSR, dir, dev, &fops_ampdu_stat); + debugfs_create_file("eeprom_param", S_IRUSR, dir, dev, + &fops_eeprom_param); +} diff --git a/drivers/net/wireless/mediatek/mt7601u/dma.c b/drivers/net/wireless/mediatek/mt7601u/dma.c new file mode 100644 index 0000000000000..7217da4f1543a --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/dma.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "dma.h" +#include "usb.h" +#include "trace.h" + +static int mt7601u_submit_rx_buf(struct mt7601u_dev *dev, + struct mt7601u_dma_buf_rx *e, gfp_t gfp); + +static unsigned int ieee80211_get_hdrlen_from_buf(const u8 *data, unsigned len) +{ + const struct ieee80211_hdr *hdr = (const struct ieee80211_hdr *)data; + unsigned int hdrlen; + + if (unlikely(len < 10)) + return 0; + hdrlen = ieee80211_hdrlen(hdr->frame_control); + if (unlikely(hdrlen > len)) + return 0; + return hdrlen; +} + +static struct sk_buff * +mt7601u_rx_skb_from_seg(struct mt7601u_dev *dev, struct mt7601u_rxwi *rxwi, + void *data, u32 seg_len, u32 truesize, struct page *p) +{ + struct sk_buff *skb; + u32 true_len, hdr_len = 0, copy, frag; + + skb = alloc_skb(p ? 128 : seg_len, GFP_ATOMIC); + if (!skb) + return NULL; + + true_len = mt76_mac_process_rx(dev, skb, data, rxwi); + if (!true_len || true_len > seg_len) + goto bad_frame; + + hdr_len = ieee80211_get_hdrlen_from_buf(data, true_len); + if (!hdr_len) + goto bad_frame; + + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) { + memcpy(skb_put(skb, hdr_len), data, hdr_len); + + data += hdr_len + 2; + true_len -= hdr_len; + hdr_len = 0; + } + + /* If not doing paged RX allocated skb will always have enough space */ + copy = (true_len <= skb_tailroom(skb)) ? true_len : hdr_len + 8; + frag = true_len - copy; + + memcpy(skb_put(skb, copy), data, copy); + data += copy; + + if (frag) { + skb_add_rx_frag(skb, 0, p, data - page_address(p), + frag, truesize); + get_page(p); + } + + return skb; + +bad_frame: + dev_err_ratelimited(dev->dev, "Error: incorrect frame len:%u hdr:%u\n", + true_len, hdr_len); + dev_kfree_skb(skb); + return NULL; +} + +static void mt7601u_rx_process_seg(struct mt7601u_dev *dev, u8 *data, + u32 seg_len, struct page *p) +{ + struct sk_buff *skb; + struct mt7601u_rxwi *rxwi; + u32 fce_info, truesize = seg_len; + + /* DMA_INFO field at the beginning of the segment contains only some of + * the information, we need to read the FCE descriptor from the end. + */ + fce_info = get_unaligned_le32(data + seg_len - MT_FCE_INFO_LEN); + seg_len -= MT_FCE_INFO_LEN; + + data += MT_DMA_HDR_LEN; + seg_len -= MT_DMA_HDR_LEN; + + rxwi = (struct mt7601u_rxwi *) data; + data += sizeof(struct mt7601u_rxwi); + seg_len -= sizeof(struct mt7601u_rxwi); + + if (unlikely(rxwi->zero[0] || rxwi->zero[1] || rxwi->zero[2])) + dev_err_once(dev->dev, "Error: RXWI zero fields are set\n"); + if (unlikely(MT76_GET(MT_RXD_INFO_TYPE, fce_info))) + dev_err_once(dev->dev, "Error: RX path seen a non-pkt urb\n"); + + trace_mt_rx(dev, rxwi, fce_info); + + skb = mt7601u_rx_skb_from_seg(dev, rxwi, data, seg_len, truesize, p); + if (!skb) + return; + + ieee80211_rx_ni(dev->hw, skb); +} + +static u16 mt7601u_rx_next_seg_len(u8 *data, u32 data_len) +{ + u32 min_seg_len = MT_DMA_HDR_LEN + MT_RX_INFO_LEN + + sizeof(struct mt7601u_rxwi) + MT_FCE_INFO_LEN; + u16 dma_len = get_unaligned_le16(data); + + if (data_len < min_seg_len || + WARN_ON(!dma_len) || + WARN_ON(dma_len + MT_DMA_HDRS > data_len) || + WARN_ON(dma_len & 0x3)) + return 0; + + return MT_DMA_HDRS + dma_len; +} + +static void +mt7601u_rx_process_entry(struct mt7601u_dev *dev, struct mt7601u_dma_buf_rx *e) +{ + u32 seg_len, data_len = e->urb->actual_length; + u8 *data = page_address(e->p); + struct page *new_p = NULL; + int cnt = 0; + + if (!test_bit(MT7601U_STATE_INITIALIZED, &dev->state)) + return; + + /* Copy if there is very little data in the buffer. */ + if (data_len > 512) + new_p = dev_alloc_pages(MT_RX_ORDER); + + while ((seg_len = mt7601u_rx_next_seg_len(data, data_len))) { + mt7601u_rx_process_seg(dev, data, seg_len, new_p ? e->p : NULL); + + data_len -= seg_len; + data += seg_len; + cnt++; + } + + if (cnt > 1) + trace_mt_rx_dma_aggr(dev, cnt, !!new_p); + + if (new_p) { + /* we have one extra ref from the allocator */ + __free_pages(e->p, MT_RX_ORDER); + + e->p = new_p; + } +} + +static struct mt7601u_dma_buf_rx * +mt7601u_rx_get_pending_entry(struct mt7601u_dev *dev) +{ + struct mt7601u_rx_queue *q = &dev->rx_q; + struct mt7601u_dma_buf_rx *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&dev->rx_lock, flags); + + if (!q->pending) + goto out; + + buf = &q->e[q->start]; + q->pending--; + q->start = (q->start + 1) % q->entries; +out: + spin_unlock_irqrestore(&dev->rx_lock, flags); + + return buf; +} + +static void mt7601u_complete_rx(struct urb *urb) +{ + struct mt7601u_dev *dev = urb->context; + struct mt7601u_rx_queue *q = &dev->rx_q; + unsigned long flags; + + spin_lock_irqsave(&dev->rx_lock, flags); + + if (mt7601u_urb_has_error(urb)) + dev_err(dev->dev, "Error: RX urb failed:%d\n", urb->status); + if (WARN_ONCE(q->e[q->end].urb != urb, "RX urb mismatch")) + goto out; + + q->end = (q->end + 1) % q->entries; + q->pending++; + tasklet_schedule(&dev->rx_tasklet); +out: + spin_unlock_irqrestore(&dev->rx_lock, flags); +} + +static void mt7601u_rx_tasklet(unsigned long data) +{ + struct mt7601u_dev *dev = (struct mt7601u_dev *) data; + struct mt7601u_dma_buf_rx *e; + + while ((e = mt7601u_rx_get_pending_entry(dev))) { + if (e->urb->status) + continue; + + mt7601u_rx_process_entry(dev, e); + mt7601u_submit_rx_buf(dev, e, GFP_ATOMIC); + } +} + +static void mt7601u_complete_tx(struct urb *urb) +{ + struct mt7601u_tx_queue *q = urb->context; + struct mt7601u_dev *dev = q->dev; + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&dev->tx_lock, flags); + + if (mt7601u_urb_has_error(urb)) + dev_err(dev->dev, "Error: TX urb failed:%d\n", urb->status); + if (WARN_ONCE(q->e[q->start].urb != urb, "TX urb mismatch")) + goto out; + + skb = q->e[q->start].skb; + trace_mt_tx_dma_done(dev, skb); + + mt7601u_tx_status(dev, skb); + + if (q->used == q->entries - q->entries / 8) + ieee80211_wake_queue(dev->hw, skb_get_queue_mapping(skb)); + + q->start = (q->start + 1) % q->entries; + q->used--; + + if (urb->status) + goto out; + + set_bit(MT7601U_STATE_MORE_STATS, &dev->state); + if (!test_and_set_bit(MT7601U_STATE_READING_STATS, &dev->state)) + queue_delayed_work(dev->stat_wq, &dev->stat_work, + msecs_to_jiffies(10)); +out: + spin_unlock_irqrestore(&dev->tx_lock, flags); +} + +static int mt7601u_dma_submit_tx(struct mt7601u_dev *dev, + struct sk_buff *skb, u8 ep) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + unsigned snd_pipe = usb_sndbulkpipe(usb_dev, dev->out_eps[ep]); + struct mt7601u_dma_buf_tx *e; + struct mt7601u_tx_queue *q = &dev->tx_q[ep]; + unsigned long flags; + int ret; + + spin_lock_irqsave(&dev->tx_lock, flags); + + if (WARN_ON(q->entries <= q->used)) { + ret = -ENOSPC; + goto out; + } + + e = &q->e[q->end]; + e->skb = skb; + usb_fill_bulk_urb(e->urb, usb_dev, snd_pipe, skb->data, skb->len, + mt7601u_complete_tx, q); + ret = usb_submit_urb(e->urb, GFP_ATOMIC); + if (ret) { + /* Special-handle ENODEV from TX urb submission because it will + * often be the first ENODEV we see after device is removed. + */ + if (ret == -ENODEV) + set_bit(MT7601U_STATE_REMOVED, &dev->state); + else + dev_err(dev->dev, "Error: TX urb submit failed:%d\n", + ret); + goto out; + } + + q->end = (q->end + 1) % q->entries; + q->used++; + + if (q->used >= q->entries) + ieee80211_stop_queue(dev->hw, skb_get_queue_mapping(skb)); +out: + spin_unlock_irqrestore(&dev->tx_lock, flags); + + return ret; +} + +/* Map hardware Q to USB endpoint number */ +static u8 q2ep(u8 qid) +{ + /* TODO: take management packets to queue 5 */ + return qid + 1; +} + +/* Map USB endpoint number to Q id in the DMA engine */ +static enum mt76_qsel ep2dmaq(u8 ep) +{ + if (ep == 5) + return MT_QSEL_MGMT; + return MT_QSEL_EDCA; +} + +int mt7601u_dma_enqueue_tx(struct mt7601u_dev *dev, struct sk_buff *skb, + struct mt76_wcid *wcid, int hw_q) +{ + u8 ep = q2ep(hw_q); + u32 dma_flags; + int ret; + + dma_flags = MT_TXD_PKT_INFO_80211; + if (wcid->hw_key_idx == 0xff) + dma_flags |= MT_TXD_PKT_INFO_WIV; + + ret = mt7601u_dma_skb_wrap_pkt(skb, ep2dmaq(ep), dma_flags); + if (ret) + return ret; + + ret = mt7601u_dma_submit_tx(dev, skb, ep); + if (ret) { + ieee80211_free_txskb(dev->hw, skb); + return ret; + } + + return 0; +} + +static void mt7601u_kill_rx(struct mt7601u_dev *dev) +{ + int i; + unsigned long flags; + + spin_lock_irqsave(&dev->rx_lock, flags); + + for (i = 0; i < dev->rx_q.entries; i++) { + int next = dev->rx_q.end; + + spin_unlock_irqrestore(&dev->rx_lock, flags); + usb_poison_urb(dev->rx_q.e[next].urb); + spin_lock_irqsave(&dev->rx_lock, flags); + } + + spin_unlock_irqrestore(&dev->rx_lock, flags); +} + +static int mt7601u_submit_rx_buf(struct mt7601u_dev *dev, + struct mt7601u_dma_buf_rx *e, gfp_t gfp) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + u8 *buf = page_address(e->p); + unsigned pipe; + int ret; + + pipe = usb_rcvbulkpipe(usb_dev, dev->in_eps[MT_EP_IN_PKT_RX]); + + usb_fill_bulk_urb(e->urb, usb_dev, pipe, buf, MT_RX_URB_SIZE, + mt7601u_complete_rx, dev); + + trace_mt_submit_urb(dev, e->urb); + ret = usb_submit_urb(e->urb, gfp); + if (ret) + dev_err(dev->dev, "Error: submit RX URB failed:%d\n", ret); + + return ret; +} + +static int mt7601u_submit_rx(struct mt7601u_dev *dev) +{ + int i, ret; + + for (i = 0; i < dev->rx_q.entries; i++) { + ret = mt7601u_submit_rx_buf(dev, &dev->rx_q.e[i], GFP_KERNEL); + if (ret) + return ret; + } + + return 0; +} + +static void mt7601u_free_rx(struct mt7601u_dev *dev) +{ + int i; + + for (i = 0; i < dev->rx_q.entries; i++) { + __free_pages(dev->rx_q.e[i].p, MT_RX_ORDER); + usb_free_urb(dev->rx_q.e[i].urb); + } +} + +static int mt7601u_alloc_rx(struct mt7601u_dev *dev) +{ + int i; + + memset(&dev->rx_q, 0, sizeof(dev->rx_q)); + dev->rx_q.dev = dev; + dev->rx_q.entries = N_RX_ENTRIES; + + for (i = 0; i < N_RX_ENTRIES; i++) { + dev->rx_q.e[i].urb = usb_alloc_urb(0, GFP_KERNEL); + dev->rx_q.e[i].p = dev_alloc_pages(MT_RX_ORDER); + + if (!dev->rx_q.e[i].urb || !dev->rx_q.e[i].p) + return -ENOMEM; + } + + return 0; +} + +static void mt7601u_free_tx_queue(struct mt7601u_tx_queue *q) +{ + int i; + + WARN_ON(q->used); + + for (i = 0; i < q->entries; i++) { + usb_poison_urb(q->e[i].urb); + usb_free_urb(q->e[i].urb); + } +} + +static void mt7601u_free_tx(struct mt7601u_dev *dev) +{ + int i; + + for (i = 0; i < __MT_EP_OUT_MAX; i++) + mt7601u_free_tx_queue(&dev->tx_q[i]); +} + +static int mt7601u_alloc_tx_queue(struct mt7601u_dev *dev, + struct mt7601u_tx_queue *q) +{ + int i; + + q->dev = dev; + q->entries = N_TX_ENTRIES; + + for (i = 0; i < N_TX_ENTRIES; i++) { + q->e[i].urb = usb_alloc_urb(0, GFP_KERNEL); + if (!q->e[i].urb) + return -ENOMEM; + } + + return 0; +} + +static int mt7601u_alloc_tx(struct mt7601u_dev *dev) +{ + int i; + + dev->tx_q = devm_kcalloc(dev->dev, __MT_EP_OUT_MAX, + sizeof(*dev->tx_q), GFP_KERNEL); + + for (i = 0; i < __MT_EP_OUT_MAX; i++) + if (mt7601u_alloc_tx_queue(dev, &dev->tx_q[i])) + return -ENOMEM; + + return 0; +} + +int mt7601u_dma_init(struct mt7601u_dev *dev) +{ + int ret = -ENOMEM; + + tasklet_init(&dev->rx_tasklet, mt7601u_rx_tasklet, (unsigned long) dev); + + ret = mt7601u_alloc_tx(dev); + if (ret) + goto err; + ret = mt7601u_alloc_rx(dev); + if (ret) + goto err; + + ret = mt7601u_submit_rx(dev); + if (ret) + goto err; + + return 0; +err: + mt7601u_dma_cleanup(dev); + return ret; +} + +void mt7601u_dma_cleanup(struct mt7601u_dev *dev) +{ + mt7601u_kill_rx(dev); + + tasklet_kill(&dev->rx_tasklet); + + mt7601u_free_rx(dev); + mt7601u_free_tx(dev); +} diff --git a/drivers/net/wireless/mediatek/mt7601u/dma.h b/drivers/net/wireless/mediatek/mt7601u/dma.h new file mode 100644 index 0000000000000..978e8a90b87f3 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/dma.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_DMA_H +#define __MT7601U_DMA_H + +#include +#include + +#include "util.h" + +#define MT_DMA_HDR_LEN 4 +#define MT_RX_INFO_LEN 4 +#define MT_FCE_INFO_LEN 4 +#define MT_DMA_HDRS (MT_DMA_HDR_LEN + MT_RX_INFO_LEN) + +/* Common Tx DMA descriptor fields */ +#define MT_TXD_INFO_LEN GENMASK(15, 0) +#define MT_TXD_INFO_D_PORT GENMASK(29, 27) +#define MT_TXD_INFO_TYPE GENMASK(31, 30) + +enum mt76_msg_port { + WLAN_PORT, + CPU_RX_PORT, + CPU_TX_PORT, + HOST_PORT, + VIRTUAL_CPU_RX_PORT, + VIRTUAL_CPU_TX_PORT, + DISCARD, +}; + +enum mt76_info_type { + DMA_PACKET, + DMA_COMMAND, +}; + +/* Tx DMA packet specific flags */ +#define MT_TXD_PKT_INFO_NEXT_VLD BIT(16) +#define MT_TXD_PKT_INFO_TX_BURST BIT(17) +#define MT_TXD_PKT_INFO_80211 BIT(19) +#define MT_TXD_PKT_INFO_TSO BIT(20) +#define MT_TXD_PKT_INFO_CSO BIT(21) +#define MT_TXD_PKT_INFO_WIV BIT(24) +#define MT_TXD_PKT_INFO_QSEL GENMASK(26, 25) + +enum mt76_qsel { + MT_QSEL_MGMT, + MT_QSEL_HCCA, + MT_QSEL_EDCA, + MT_QSEL_EDCA_2, +}; + +/* Tx DMA MCU command specific flags */ +#define MT_TXD_CMD_INFO_SEQ GENMASK(19, 16) +#define MT_TXD_CMD_INFO_TYPE GENMASK(26, 20) + +static inline int mt7601u_dma_skb_wrap(struct sk_buff *skb, + enum mt76_msg_port d_port, + enum mt76_info_type type, u32 flags) +{ + u32 info; + + /* Buffer layout: + * | 4B | xfer len | pad | 4B | + * | TXINFO | pkt/cmd | zero pad to 4B | zero | + * + * length field of TXINFO should be set to 'xfer len'. + */ + + info = flags | + MT76_SET(MT_TXD_INFO_LEN, round_up(skb->len, 4)) | + MT76_SET(MT_TXD_INFO_D_PORT, d_port) | + MT76_SET(MT_TXD_INFO_TYPE, type); + + put_unaligned_le32(info, skb_push(skb, sizeof(info))); + return skb_put_padto(skb, round_up(skb->len, 4) + 4); +} + +static inline int +mt7601u_dma_skb_wrap_pkt(struct sk_buff *skb, enum mt76_qsel qsel, u32 flags) +{ + flags |= MT76_SET(MT_TXD_PKT_INFO_QSEL, qsel); + return mt7601u_dma_skb_wrap(skb, WLAN_PORT, DMA_PACKET, flags); +} + +/* Common Rx DMA descriptor fields */ +#define MT_RXD_INFO_LEN GENMASK(13, 0) +#define MT_RXD_INFO_PCIE_INTR BIT(24) +#define MT_RXD_INFO_QSEL GENMASK(26, 25) +#define MT_RXD_INFO_PORT GENMASK(29, 27) +#define MT_RXD_INFO_TYPE GENMASK(31, 30) + +/* Rx DMA packet specific flags */ +#define MT_RXD_PKT_INFO_UDP_ERR BIT(16) +#define MT_RXD_PKT_INFO_TCP_ERR BIT(17) +#define MT_RXD_PKT_INFO_IP_ERR BIT(18) +#define MT_RXD_PKT_INFO_PKT_80211 BIT(19) +#define MT_RXD_PKT_INFO_L3L4_DONE BIT(20) +#define MT_RXD_PKT_INFO_MAC_LEN GENMASK(23, 21) + +/* Rx DMA MCU command specific flags */ +#define MT_RXD_CMD_INFO_SELF_GEN BIT(15) +#define MT_RXD_CMD_INFO_CMD_SEQ GENMASK(19, 16) +#define MT_RXD_CMD_INFO_EVT_TYPE GENMASK(23, 20) + +enum mt76_evt_type { + CMD_DONE, + CMD_ERROR, + CMD_RETRY, + EVENT_PWR_RSP, + EVENT_WOW_RSP, + EVENT_CARRIER_DETECT_RSP, + EVENT_DFS_DETECT_RSP, +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/eeprom.c b/drivers/net/wireless/mediatek/mt7601u/eeprom.c new file mode 100644 index 0000000000000..8d8ee0344f7ba --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/eeprom.c @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "eeprom.h" + +static bool +field_valid(u8 val) +{ + return val != 0xff; +} + +static s8 +field_validate(u8 val) +{ + if (!field_valid(val)) + return 0; + + return val; +} + +static int +mt7601u_efuse_read(struct mt7601u_dev *dev, u16 addr, u8 *data, + enum mt7601u_eeprom_access_modes mode) +{ + u32 val; + int i; + + val = mt76_rr(dev, MT_EFUSE_CTRL); + val &= ~(MT_EFUSE_CTRL_AIN | + MT_EFUSE_CTRL_MODE); + val |= MT76_SET(MT_EFUSE_CTRL_AIN, addr & ~0xf) | + MT76_SET(MT_EFUSE_CTRL_MODE, mode) | + MT_EFUSE_CTRL_KICK; + mt76_wr(dev, MT_EFUSE_CTRL, val); + + if (!mt76_poll(dev, MT_EFUSE_CTRL, MT_EFUSE_CTRL_KICK, 0, 1000)) + return -ETIMEDOUT; + + val = mt76_rr(dev, MT_EFUSE_CTRL); + if ((val & MT_EFUSE_CTRL_AOUT) == MT_EFUSE_CTRL_AOUT) { + /* Parts of eeprom not in the usage map (0x80-0xc0,0xf0) + * will not return valid data but it's ok. + */ + memset(data, 0xff, 16); + return 0; + } + + for (i = 0; i < 4; i++) { + val = mt76_rr(dev, MT_EFUSE_DATA(i)); + put_unaligned_le32(val, data + 4 * i); + } + + return 0; +} + +static int +mt7601u_efuse_physical_size_check(struct mt7601u_dev *dev) +{ + const int map_reads = DIV_ROUND_UP(MT_EFUSE_USAGE_MAP_SIZE, 16); + u8 data[map_reads * 16]; + int ret, i; + u32 start = 0, end = 0, cnt_free; + + for (i = 0; i < map_reads; i++) { + ret = mt7601u_efuse_read(dev, MT_EE_USAGE_MAP_START + i * 16, + data + i * 16, MT_EE_PHYSICAL_READ); + if (ret) + return ret; + } + + for (i = 0; i < MT_EFUSE_USAGE_MAP_SIZE; i++) + if (!data[i]) { + if (!start) + start = MT_EE_USAGE_MAP_START + i; + end = MT_EE_USAGE_MAP_START + i; + } + cnt_free = end - start + 1; + + if (MT_EFUSE_USAGE_MAP_SIZE - cnt_free < 5) { + dev_err(dev->dev, "Error: your device needs default EEPROM file and this driver doesn't support it!\n"); + return -EINVAL; + } + + return 0; +} + +static bool +mt7601u_has_tssi(struct mt7601u_dev *dev, u8 *eeprom) +{ + u16 nic_conf1 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_1); + + return ~nic_conf1 && (nic_conf1 & MT_EE_NIC_CONF_1_TX_ALC_EN); +} + +static void +mt7601u_set_chip_cap(struct mt7601u_dev *dev, u8 *eeprom) +{ + u16 nic_conf0 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_0); + u16 nic_conf1 = get_unaligned_le16(eeprom + MT_EE_NIC_CONF_1); + + if (!field_valid(nic_conf1 & 0xff)) + nic_conf1 &= 0xff00; + + dev->ee->tssi_enabled = mt7601u_has_tssi(dev, eeprom) && + !(nic_conf1 & MT_EE_NIC_CONF_1_TEMP_TX_ALC); + + if (nic_conf1 & MT_EE_NIC_CONF_1_HW_RF_CTRL) + dev_err(dev->dev, + "Error: this driver does not support HW RF ctrl\n"); + + if (!field_valid(nic_conf0 >> 8)) + return; + + if (MT76_GET(MT_EE_NIC_CONF_0_RX_PATH, nic_conf0) > 1 || + MT76_GET(MT_EE_NIC_CONF_0_TX_PATH, nic_conf0) > 1) + dev_err(dev->dev, + "Error: device has more than 1 RX/TX stream!\n"); +} + +static int +mt7601u_set_macaddr(struct mt7601u_dev *dev, const u8 *eeprom) +{ + const void *src = eeprom + MT_EE_MAC_ADDR; + + ether_addr_copy(dev->macaddr, src); + + if (!is_valid_ether_addr(dev->macaddr)) { + eth_random_addr(dev->macaddr); + dev_info(dev->dev, + "Invalid MAC address, using random address %pM\n", + dev->macaddr); + } + + mt76_wr(dev, MT_MAC_ADDR_DW0, get_unaligned_le32(dev->macaddr)); + mt76_wr(dev, MT_MAC_ADDR_DW1, get_unaligned_le16(dev->macaddr + 4) | + MT76_SET(MT_MAC_ADDR_DW1_U2ME_MASK, 0xff)); + + return 0; +} + +static void mt7601u_set_channel_target_power(struct mt7601u_dev *dev, + u8 *eeprom, u8 max_pwr) +{ + u8 trgt_pwr = eeprom[MT_EE_TX_TSSI_TARGET_POWER]; + + if (trgt_pwr > max_pwr || !trgt_pwr) { + dev_warn(dev->dev, "Error: EEPROM trgt power invalid %hhx!\n", + trgt_pwr); + trgt_pwr = 0x20; + } + + memset(dev->ee->chan_pwr, trgt_pwr, sizeof(dev->ee->chan_pwr)); +} + +static void +mt7601u_set_channel_power(struct mt7601u_dev *dev, u8 *eeprom) +{ + u32 i, val; + u8 max_pwr; + + val = mt7601u_rr(dev, MT_TX_ALC_CFG_0); + max_pwr = MT76_GET(MT_TX_ALC_CFG_0_LIMIT_0, val); + + if (mt7601u_has_tssi(dev, eeprom)) { + mt7601u_set_channel_target_power(dev, eeprom, max_pwr); + return; + } + + for (i = 0; i < 14; i++) { + s8 power = field_validate(eeprom[MT_EE_TX_POWER_OFFSET + i]); + + if (power > max_pwr || power < 0) + power = MT7601U_DEFAULT_TX_POWER; + + dev->ee->chan_pwr[i] = power; + } +} + +static void +mt7601u_set_country_reg(struct mt7601u_dev *dev, u8 *eeprom) +{ + /* Note: - region 31 is not valid for mt7601u (see rtmp_init.c) + * - comments in rtmp_def.h are incorrect (see rt_channel.c) + */ + static const struct reg_channel_bounds chan_bounds[] = { + /* EEPROM country regions 0 - 7 */ + { 1, 11 }, { 1, 13 }, { 10, 2 }, { 10, 4 }, + { 14, 1 }, { 1, 14 }, { 3, 7 }, { 5, 9 }, + /* EEPROM country regions 32 - 33 */ + { 1, 11 }, { 1, 14 } + }; + u8 val = eeprom[MT_EE_COUNTRY_REGION]; + int idx = -1; + + if (val < 8) + idx = val; + if (val > 31 && val < 33) + idx = val - 32 + 8; + + if (idx != -1) + dev_info(dev->dev, + "EEPROM country region %02hhx (channels %hhd-%hhd)\n", + val, chan_bounds[idx].start, + chan_bounds[idx].start + chan_bounds[idx].num - 1); + else + idx = 5; /* channels 1 - 14 */ + + dev->ee->reg = chan_bounds[idx]; + + /* TODO: country region 33 is special - phy should be set to B-mode + * before entering channel 14 (see sta/connect.c) + */ +} + +static void +mt7601u_set_rf_freq_off(struct mt7601u_dev *dev, u8 *eeprom) +{ + u8 comp; + + dev->ee->rf_freq_off = field_validate(eeprom[MT_EE_FREQ_OFFSET]); + comp = field_validate(eeprom[MT_EE_FREQ_OFFSET_COMPENSATION]); + + if (comp & BIT(7)) + dev->ee->rf_freq_off -= comp & 0x7f; + else + dev->ee->rf_freq_off += comp; +} + +static void +mt7601u_set_rssi_offset(struct mt7601u_dev *dev, u8 *eeprom) +{ + int i; + s8 *rssi_offset = dev->ee->rssi_offset; + + for (i = 0; i < 2; i++) { + rssi_offset[i] = eeprom[MT_EE_RSSI_OFFSET + i]; + + if (rssi_offset[i] < -10 || rssi_offset[i] > 10) { + dev_warn(dev->dev, + "Warning: EEPROM RSSI is invalid %02hhx\n", + rssi_offset[i]); + rssi_offset[i] = 0; + } + } +} + +static void +mt7601u_extra_power_over_mac(struct mt7601u_dev *dev) +{ + u32 val; + + val = ((mt7601u_rr(dev, MT_TX_PWR_CFG_1) & 0x0000ff00) >> 8); + val |= ((mt7601u_rr(dev, MT_TX_PWR_CFG_2) & 0x0000ff00) << 8); + mt7601u_wr(dev, MT_TX_PWR_CFG_7, val); + + val = ((mt7601u_rr(dev, MT_TX_PWR_CFG_4) & 0x0000ff00) >> 8); + mt7601u_wr(dev, MT_TX_PWR_CFG_9, val); +} + +static void +mt7601u_set_power_rate(struct power_per_rate *rate, s8 delta, u8 value) +{ + /* Invalid? Note: vendor driver does not handle this */ + if (value == 0xff) + return; + + rate->raw = s6_validate(value); + rate->bw20 = s6_to_int(value); + /* Note: vendor driver does cap the value to s6 right away */ + rate->bw40 = rate->bw20 + delta; +} + +static void +mt7601u_save_power_rate(struct mt7601u_dev *dev, s8 delta, u32 val, int i) +{ + struct mt7601u_rate_power *t = &dev->ee->power_rate_table; + + switch (i) { + case 0: + mt7601u_set_power_rate(&t->cck[0], delta, (val >> 0) & 0xff); + mt7601u_set_power_rate(&t->cck[1], delta, (val >> 8) & 0xff); + /* Save cck bw20 for fixups of channel 14 */ + dev->ee->real_cck_bw20[0] = t->cck[0].bw20; + dev->ee->real_cck_bw20[1] = t->cck[1].bw20; + + mt7601u_set_power_rate(&t->ofdm[0], delta, (val >> 16) & 0xff); + mt7601u_set_power_rate(&t->ofdm[1], delta, (val >> 24) & 0xff); + break; + case 1: + mt7601u_set_power_rate(&t->ofdm[2], delta, (val >> 0) & 0xff); + mt7601u_set_power_rate(&t->ofdm[3], delta, (val >> 8) & 0xff); + mt7601u_set_power_rate(&t->ht[0], delta, (val >> 16) & 0xff); + mt7601u_set_power_rate(&t->ht[1], delta, (val >> 24) & 0xff); + break; + case 2: + mt7601u_set_power_rate(&t->ht[2], delta, (val >> 0) & 0xff); + mt7601u_set_power_rate(&t->ht[3], delta, (val >> 8) & 0xff); + break; + } +} + +static s8 +get_delta(u8 val) +{ + s8 ret; + + if (!field_valid(val) || !(val & BIT(7))) + return 0; + + ret = val & 0x1f; + if (ret > 8) + ret = 8; + if (val & BIT(6)) + ret = -ret; + + return ret; +} + +static void +mt7601u_config_tx_power_per_rate(struct mt7601u_dev *dev, u8 *eeprom) +{ + u32 val; + s8 bw40_delta; + int i; + + bw40_delta = get_delta(eeprom[MT_EE_TX_POWER_DELTA_BW40]); + + for (i = 0; i < 5; i++) { + val = get_unaligned_le32(eeprom + MT_EE_TX_POWER_BYRATE(i)); + + mt7601u_save_power_rate(dev, bw40_delta, val, i); + + if (~val) + mt7601u_wr(dev, MT_TX_PWR_CFG_0 + i * 4, val); + } + + mt7601u_extra_power_over_mac(dev); +} + +static void +mt7601u_init_tssi_params(struct mt7601u_dev *dev, u8 *eeprom) +{ + struct tssi_data *d = &dev->ee->tssi_data; + + if (!dev->ee->tssi_enabled) + return; + + d->slope = eeprom[MT_EE_TX_TSSI_SLOPE]; + d->tx0_delta_offset = eeprom[MT_EE_TX_TSSI_OFFSET] * 1024; + d->offset[0] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP]; + d->offset[1] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP + 1]; + d->offset[2] = eeprom[MT_EE_TX_TSSI_OFFSET_GROUP + 2]; +} + +int +mt7601u_eeprom_init(struct mt7601u_dev *dev) +{ + u8 *eeprom; + int i, ret; + + ret = mt7601u_efuse_physical_size_check(dev); + if (ret) + return ret; + + dev->ee = devm_kzalloc(dev->dev, sizeof(*dev->ee), GFP_KERNEL); + if (!dev->ee) + return -ENOMEM; + + eeprom = kmalloc(MT7601U_EEPROM_SIZE, GFP_KERNEL); + if (!eeprom) + return -ENOMEM; + + for (i = 0; i + 16 <= MT7601U_EEPROM_SIZE; i += 16) { + ret = mt7601u_efuse_read(dev, i, eeprom + i, MT_EE_READ); + if (ret) + goto out; + } + + if (eeprom[MT_EE_VERSION_EE] > MT7601U_EE_MAX_VER) + dev_warn(dev->dev, + "Warning: unsupported EEPROM version %02hhx\n", + eeprom[MT_EE_VERSION_EE]); + dev_info(dev->dev, "EEPROM ver:%02hhx fae:%02hhx\n", + eeprom[MT_EE_VERSION_EE], eeprom[MT_EE_VERSION_FAE]); + + mt7601u_set_macaddr(dev, eeprom); + mt7601u_set_chip_cap(dev, eeprom); + mt7601u_set_channel_power(dev, eeprom); + mt7601u_set_country_reg(dev, eeprom); + mt7601u_set_rf_freq_off(dev, eeprom); + mt7601u_set_rssi_offset(dev, eeprom); + dev->ee->ref_temp = eeprom[MT_EE_REF_TEMP]; + dev->ee->lna_gain = eeprom[MT_EE_LNA_GAIN]; + + mt7601u_config_tx_power_per_rate(dev, eeprom); + + mt7601u_init_tssi_params(dev, eeprom); +out: + kfree(eeprom); + return ret; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/eeprom.h b/drivers/net/wireless/mediatek/mt7601u/eeprom.h new file mode 100644 index 0000000000000..662d12703b69c --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/eeprom.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_EEPROM_H +#define __MT7601U_EEPROM_H + +struct mt7601u_dev; + +#define MT7601U_EE_MAX_VER 0x0c +#define MT7601U_EEPROM_SIZE 256 + +#define MT7601U_DEFAULT_TX_POWER 6 + +enum mt76_eeprom_field { + MT_EE_CHIP_ID = 0x00, + MT_EE_VERSION_FAE = 0x02, + MT_EE_VERSION_EE = 0x03, + MT_EE_MAC_ADDR = 0x04, + MT_EE_NIC_CONF_0 = 0x34, + MT_EE_NIC_CONF_1 = 0x36, + MT_EE_COUNTRY_REGION = 0x39, + MT_EE_FREQ_OFFSET = 0x3a, + MT_EE_NIC_CONF_2 = 0x42, + + MT_EE_LNA_GAIN = 0x44, + MT_EE_RSSI_OFFSET = 0x46, + + MT_EE_TX_POWER_DELTA_BW40 = 0x50, + MT_EE_TX_POWER_OFFSET = 0x52, + + MT_EE_TX_TSSI_SLOPE = 0x6e, + MT_EE_TX_TSSI_OFFSET_GROUP = 0x6f, + MT_EE_TX_TSSI_OFFSET = 0x76, + + MT_EE_TX_TSSI_TARGET_POWER = 0xd0, + MT_EE_REF_TEMP = 0xd1, + MT_EE_FREQ_OFFSET_COMPENSATION = 0xdb, + MT_EE_TX_POWER_BYRATE_BASE = 0xde, + + MT_EE_USAGE_MAP_START = 0x1e0, + MT_EE_USAGE_MAP_END = 0x1fc, +}; + +#define MT_EE_NIC_CONF_0_RX_PATH GENMASK(3, 0) +#define MT_EE_NIC_CONF_0_TX_PATH GENMASK(7, 4) +#define MT_EE_NIC_CONF_0_BOARD_TYPE GENMASK(13, 12) + +#define MT_EE_NIC_CONF_1_HW_RF_CTRL BIT(0) +#define MT_EE_NIC_CONF_1_TEMP_TX_ALC BIT(1) +#define MT_EE_NIC_CONF_1_LNA_EXT_2G BIT(2) +#define MT_EE_NIC_CONF_1_LNA_EXT_5G BIT(3) +#define MT_EE_NIC_CONF_1_TX_ALC_EN BIT(13) + +#define MT_EE_NIC_CONF_2_RX_STREAM GENMASK(3, 0) +#define MT_EE_NIC_CONF_2_TX_STREAM GENMASK(7, 4) +#define MT_EE_NIC_CONF_2_HW_ANTDIV BIT(8) +#define MT_EE_NIC_CONF_2_XTAL_OPTION GENMASK(10, 9) +#define MT_EE_NIC_CONF_2_TEMP_DISABLE BIT(11) +#define MT_EE_NIC_CONF_2_COEX_METHOD GENMASK(15, 13) + +#define MT_EE_TX_POWER_BYRATE(i) (MT_EE_TX_POWER_BYRATE_BASE + \ + (i) * 4) + +#define MT_EFUSE_USAGE_MAP_SIZE (MT_EE_USAGE_MAP_END - \ + MT_EE_USAGE_MAP_START + 1) + +enum mt7601u_eeprom_access_modes { + MT_EE_READ = 0, + MT_EE_PHYSICAL_READ = 1, +}; + +struct power_per_rate { + u8 raw; /* validated s6 value */ + s8 bw20; /* sign-extended int */ + s8 bw40; /* sign-extended int */ +}; + +/* Power per rate - one value per two rates */ +struct mt7601u_rate_power { + struct power_per_rate cck[2]; + struct power_per_rate ofdm[4]; + struct power_per_rate ht[4]; +}; + +struct reg_channel_bounds { + u8 start; + u8 num; +}; + +struct mt7601u_eeprom_params { + bool tssi_enabled; + u8 rf_freq_off; + s8 rssi_offset[2]; + s8 ref_temp; + s8 lna_gain; + + u8 chan_pwr[14]; + struct mt7601u_rate_power power_rate_table; + s8 real_cck_bw20[2]; + + /* TSSI stuff - only with internal TX ALC */ + struct tssi_data { + int tx0_delta_offset; + u8 slope; + u8 offset[3]; + } tssi_data; + + struct reg_channel_bounds reg; +}; + +int mt7601u_eeprom_init(struct mt7601u_dev *dev); + +static inline u32 s6_validate(u32 reg) +{ + WARN_ON(reg & ~GENMASK(5, 0)); + return reg & GENMASK(5, 0); +} + +static inline int s6_to_int(u32 reg) +{ + int s6; + + s6 = s6_validate(reg); + if (s6 & BIT(5)) + s6 -= BIT(6); + + return s6; +} + +static inline u32 int_to_s6(int val) +{ + if (val < -0x20) + return 0x20; + if (val > 0x1f) + return 0x1f; + + return val & 0x3f; +} + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/init.c b/drivers/net/wireless/mediatek/mt7601u/init.c new file mode 100644 index 0000000000000..45eb0796a2e54 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/init.c @@ -0,0 +1,628 @@ +/* + * (c) Copyright 2002-2010, Ralink Technology, Inc. + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "eeprom.h" +#include "trace.h" +#include "mcu.h" + +#include "initvals.h" + +static void +mt7601u_set_wlan_state(struct mt7601u_dev *dev, u32 val, bool enable) +{ + int i; + + /* Note: we don't turn off WLAN_CLK because that makes the device + * not respond properly on the probe path. + * In case anyone (PSM?) wants to use this function we can + * bring the clock stuff back and fixup the probe path. + */ + + if (enable) + val |= (MT_WLAN_FUN_CTRL_WLAN_EN | + MT_WLAN_FUN_CTRL_WLAN_CLK_EN); + else + val &= ~(MT_WLAN_FUN_CTRL_WLAN_EN); + + mt7601u_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); + + if (enable) { + set_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state); + } else { + clear_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state); + return; + } + + for (i = 200; i; i--) { + val = mt7601u_rr(dev, MT_CMB_CTRL); + + if (val & MT_CMB_CTRL_XTAL_RDY && val & MT_CMB_CTRL_PLL_LD) + break; + + udelay(20); + } + + /* Note: vendor driver tries to disable/enable wlan here and retry + * but the code which does it is so buggy it must have never + * triggered, so don't bother. + */ + if (!i) + dev_err(dev->dev, "Error: PLL and XTAL check failed!\n"); +} + +static void mt7601u_chip_onoff(struct mt7601u_dev *dev, bool enable, bool reset) +{ + u32 val; + + mutex_lock(&dev->hw_atomic_mutex); + + val = mt7601u_rr(dev, MT_WLAN_FUN_CTRL); + + if (reset) { + val |= MT_WLAN_FUN_CTRL_GPIO_OUT_EN; + val &= ~MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL; + + if (val & MT_WLAN_FUN_CTRL_WLAN_EN) { + val |= (MT_WLAN_FUN_CTRL_WLAN_RESET | + MT_WLAN_FUN_CTRL_WLAN_RESET_RF); + mt7601u_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); + + val &= ~(MT_WLAN_FUN_CTRL_WLAN_RESET | + MT_WLAN_FUN_CTRL_WLAN_RESET_RF); + } + } + + mt7601u_wr(dev, MT_WLAN_FUN_CTRL, val); + udelay(20); + + mt7601u_set_wlan_state(dev, val, enable); + + mutex_unlock(&dev->hw_atomic_mutex); +} + +static void mt7601u_reset_csr_bbp(struct mt7601u_dev *dev) +{ + mt7601u_wr(dev, MT_MAC_SYS_CTRL, (MT_MAC_SYS_CTRL_RESET_CSR | + MT_MAC_SYS_CTRL_RESET_BBP)); + mt7601u_wr(dev, MT_USB_DMA_CFG, 0); + msleep(1); + mt7601u_wr(dev, MT_MAC_SYS_CTRL, 0); +} + +static void mt7601u_init_usb_dma(struct mt7601u_dev *dev) +{ + u32 val; + + val = MT76_SET(MT_USB_DMA_CFG_RX_BULK_AGG_TOUT, MT_USB_AGGR_TIMEOUT) | + MT76_SET(MT_USB_DMA_CFG_RX_BULK_AGG_LMT, MT_USB_AGGR_SIZE_LIMIT) | + MT_USB_DMA_CFG_RX_BULK_EN | + MT_USB_DMA_CFG_TX_BULK_EN; + if (dev->in_max_packet == 512) + val |= MT_USB_DMA_CFG_RX_BULK_AGG_EN; + mt7601u_wr(dev, MT_USB_DMA_CFG, val); + + val |= MT_USB_DMA_CFG_UDMA_RX_WL_DROP; + mt7601u_wr(dev, MT_USB_DMA_CFG, val); + val &= ~MT_USB_DMA_CFG_UDMA_RX_WL_DROP; + mt7601u_wr(dev, MT_USB_DMA_CFG, val); +} + +static int mt7601u_init_bbp(struct mt7601u_dev *dev) +{ + int ret; + + ret = mt7601u_wait_bbp_ready(dev); + if (ret) + return ret; + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, bbp_common_vals, + ARRAY_SIZE(bbp_common_vals)); + if (ret) + return ret; + + return mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, bbp_chip_vals, + ARRAY_SIZE(bbp_chip_vals)); +} + +static void +mt76_init_beacon_offsets(struct mt7601u_dev *dev) +{ + u16 base = MT_BEACON_BASE; + u32 regs[4] = {}; + int i; + + for (i = 0; i < 16; i++) { + u16 addr = dev->beacon_offsets[i]; + + regs[i / 4] |= ((addr - base) / 64) << (8 * (i % 4)); + } + + for (i = 0; i < 4; i++) + mt7601u_wr(dev, MT_BCN_OFFSET(i), regs[i]); +} + +static int mt7601u_write_mac_initvals(struct mt7601u_dev *dev) +{ + int ret; + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_WLAN, mac_common_vals, + ARRAY_SIZE(mac_common_vals)); + if (ret) + return ret; + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_WLAN, + mac_chip_vals, ARRAY_SIZE(mac_chip_vals)); + if (ret) + return ret; + + mt76_init_beacon_offsets(dev); + + mt7601u_wr(dev, MT_AUX_CLK_CFG, 0); + + return 0; +} + +static int mt7601u_init_wcid_mem(struct mt7601u_dev *dev) +{ + u32 *vals; + int i, ret; + + vals = kmalloc(sizeof(*vals) * N_WCIDS * 2, GFP_KERNEL); + if (!vals) + return -ENOMEM; + + for (i = 0; i < N_WCIDS; i++) { + vals[i * 2] = 0xffffffff; + vals[i * 2 + 1] = 0x00ffffff; + } + + ret = mt7601u_burst_write_regs(dev, MT_WCID_ADDR_BASE, + vals, N_WCIDS * 2); + kfree(vals); + + return ret; +} + +static int mt7601u_init_key_mem(struct mt7601u_dev *dev) +{ + u32 vals[4] = {}; + + return mt7601u_burst_write_regs(dev, MT_SKEY_MODE_BASE_0, + vals, ARRAY_SIZE(vals)); +} + +static int mt7601u_init_wcid_attr_mem(struct mt7601u_dev *dev) +{ + u32 *vals; + int i, ret; + + vals = kmalloc(sizeof(*vals) * N_WCIDS * 2, GFP_KERNEL); + if (!vals) + return -ENOMEM; + + for (i = 0; i < N_WCIDS * 2; i++) + vals[i] = 1; + + ret = mt7601u_burst_write_regs(dev, MT_WCID_ATTR_BASE, + vals, N_WCIDS * 2); + kfree(vals); + + return ret; +} + +static void mt7601u_reset_counters(struct mt7601u_dev *dev) +{ + mt7601u_rr(dev, MT_RX_STA_CNT0); + mt7601u_rr(dev, MT_RX_STA_CNT1); + mt7601u_rr(dev, MT_RX_STA_CNT2); + mt7601u_rr(dev, MT_TX_STA_CNT0); + mt7601u_rr(dev, MT_TX_STA_CNT1); + mt7601u_rr(dev, MT_TX_STA_CNT2); +} + +int mt7601u_mac_start(struct mt7601u_dev *dev) +{ + mt7601u_wr(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_TX); + + if (!mt76_poll(dev, MT_WPDMA_GLO_CFG, MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY, 0, 200000)) + return -ETIMEDOUT; + + dev->rxfilter = MT_RX_FILTR_CFG_CRC_ERR | + MT_RX_FILTR_CFG_PHY_ERR | MT_RX_FILTR_CFG_PROMISC | + MT_RX_FILTR_CFG_VER_ERR | MT_RX_FILTR_CFG_DUP | + MT_RX_FILTR_CFG_CFACK | MT_RX_FILTR_CFG_CFEND | + MT_RX_FILTR_CFG_ACK | MT_RX_FILTR_CFG_CTS | + MT_RX_FILTR_CFG_RTS | MT_RX_FILTR_CFG_PSPOLL | + MT_RX_FILTR_CFG_BA | MT_RX_FILTR_CFG_CTRL_RSV; + mt7601u_wr(dev, MT_RX_FILTR_CFG, dev->rxfilter); + + mt7601u_wr(dev, MT_MAC_SYS_CTRL, + MT_MAC_SYS_CTRL_ENABLE_TX | MT_MAC_SYS_CTRL_ENABLE_RX); + + if (!mt76_poll(dev, MT_WPDMA_GLO_CFG, MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY, 0, 50)) + return -ETIMEDOUT; + + return 0; +} + +static void mt7601u_mac_stop_hw(struct mt7601u_dev *dev) +{ + int i, ok; + + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return; + + mt76_clear(dev, MT_BEACON_TIME_CFG, MT_BEACON_TIME_CFG_TIMER_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | MT_BEACON_TIME_CFG_TBTT_EN | + MT_BEACON_TIME_CFG_BEACON_TX); + + if (!mt76_poll(dev, MT_USB_DMA_CFG, MT_USB_DMA_CFG_TX_BUSY, 0, 1000)) + dev_warn(dev->dev, "Warning: TX DMA did not stop!\n"); + + /* Page count on TxQ */ + i = 200; + while (i-- && ((mt76_rr(dev, 0x0438) & 0xffffffff) || + (mt76_rr(dev, 0x0a30) & 0x000000ff) || + (mt76_rr(dev, 0x0a34) & 0x00ff00ff))) + msleep(10); + + if (!mt76_poll(dev, MT_MAC_STATUS, MT_MAC_STATUS_TX, 0, 1000)) + dev_warn(dev->dev, "Warning: MAC TX did not stop!\n"); + + mt76_clear(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_RX | + MT_MAC_SYS_CTRL_ENABLE_TX); + + /* Page count on RxQ */ + ok = 0; + i = 200; + while (i--) { + if ((mt76_rr(dev, 0x0430) & 0x00ff0000) || + (mt76_rr(dev, 0x0a30) & 0xffffffff) || + (mt76_rr(dev, 0x0a34) & 0xffffffff)) + ok++; + if (ok > 6) + break; + + msleep(1); + } + + if (!mt76_poll(dev, MT_MAC_STATUS, MT_MAC_STATUS_RX, 0, 1000)) + dev_warn(dev->dev, "Warning: MAC RX did not stop!\n"); + + if (!mt76_poll(dev, MT_USB_DMA_CFG, MT_USB_DMA_CFG_RX_BUSY, 0, 1000)) + dev_warn(dev->dev, "Warning: RX DMA did not stop!\n"); +} + +void mt7601u_mac_stop(struct mt7601u_dev *dev) +{ + mt7601u_mac_stop_hw(dev); + flush_delayed_work(&dev->stat_work); + cancel_delayed_work_sync(&dev->stat_work); +} + +static void mt7601u_stop_hardware(struct mt7601u_dev *dev) +{ + mt7601u_chip_onoff(dev, false, false); +} + +int mt7601u_init_hardware(struct mt7601u_dev *dev) +{ + static const u16 beacon_offsets[16] = { + /* 512 byte per beacon */ + 0xc000, 0xc200, 0xc400, 0xc600, + 0xc800, 0xca00, 0xcc00, 0xce00, + 0xd000, 0xd200, 0xd400, 0xd600, + 0xd800, 0xda00, 0xdc00, 0xde00 + }; + int ret; + + dev->beacon_offsets = beacon_offsets; + + mt7601u_chip_onoff(dev, true, false); + + ret = mt7601u_wait_asic_ready(dev); + if (ret) + goto err; + ret = mt7601u_mcu_init(dev); + if (ret) + goto err; + + if (!mt76_poll_msec(dev, MT_WPDMA_GLO_CFG, + MT_WPDMA_GLO_CFG_TX_DMA_BUSY | + MT_WPDMA_GLO_CFG_RX_DMA_BUSY, 0, 100)) { + ret = -EIO; + goto err; + } + + /* Wait for ASIC ready after FW load. */ + ret = mt7601u_wait_asic_ready(dev); + if (ret) + goto err; + + mt7601u_reset_csr_bbp(dev); + mt7601u_init_usb_dma(dev); + + ret = mt7601u_mcu_cmd_init(dev); + if (ret) + goto err; + ret = mt7601u_dma_init(dev); + if (ret) + goto err_mcu; + ret = mt7601u_write_mac_initvals(dev); + if (ret) + goto err_rx; + + if (!mt76_poll_msec(dev, MT_MAC_STATUS, + MT_MAC_STATUS_TX | MT_MAC_STATUS_RX, 0, 100)) { + ret = -EIO; + goto err_rx; + } + + ret = mt7601u_init_bbp(dev); + if (ret) + goto err_rx; + ret = mt7601u_init_wcid_mem(dev); + if (ret) + goto err_rx; + ret = mt7601u_init_key_mem(dev); + if (ret) + goto err_rx; + ret = mt7601u_init_wcid_attr_mem(dev); + if (ret) + goto err_rx; + + mt76_clear(dev, MT_BEACON_TIME_CFG, (MT_BEACON_TIME_CFG_TIMER_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | + MT_BEACON_TIME_CFG_TBTT_EN | + MT_BEACON_TIME_CFG_BEACON_TX)); + + mt7601u_reset_counters(dev); + + mt7601u_rmw(dev, MT_US_CYC_CFG, MT_US_CYC_CNT, 0x1e); + + mt7601u_wr(dev, MT_TXOP_CTRL_CFG, MT76_SET(MT_TXOP_TRUN_EN, 0x3f) | + MT76_SET(MT_TXOP_EXT_CCA_DLY, 0x58)); + + ret = mt7601u_eeprom_init(dev); + if (ret) + goto err_rx; + + ret = mt7601u_phy_init(dev); + if (ret) + goto err_rx; + + mt7601u_set_rx_path(dev, 0); + mt7601u_set_tx_dac(dev, 0); + + mt7601u_mac_set_ctrlch(dev, false); + mt7601u_bbp_set_ctrlch(dev, false); + mt7601u_bbp_set_bw(dev, MT_BW_20); + + return 0; + +err_rx: + mt7601u_dma_cleanup(dev); +err_mcu: + mt7601u_mcu_cmd_deinit(dev); +err: + mt7601u_chip_onoff(dev, false, false); + return ret; +} + +void mt7601u_cleanup(struct mt7601u_dev *dev) +{ + if (!test_and_clear_bit(MT7601U_STATE_INITIALIZED, &dev->state)) + return; + + mt7601u_stop_hardware(dev); + mt7601u_dma_cleanup(dev); + mt7601u_mcu_cmd_deinit(dev); +} + +struct mt7601u_dev *mt7601u_alloc_device(struct device *pdev) +{ + struct ieee80211_hw *hw; + struct mt7601u_dev *dev; + + hw = ieee80211_alloc_hw(sizeof(*dev), &mt7601u_ops); + if (!hw) + return NULL; + + dev = hw->priv; + dev->dev = pdev; + dev->hw = hw; + mutex_init(&dev->vendor_req_mutex); + mutex_init(&dev->reg_atomic_mutex); + mutex_init(&dev->hw_atomic_mutex); + mutex_init(&dev->mutex); + spin_lock_init(&dev->tx_lock); + spin_lock_init(&dev->rx_lock); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->con_mon_lock); + atomic_set(&dev->avg_ampdu_len, 1); + + dev->stat_wq = alloc_workqueue("mt7601u", WQ_UNBOUND, 0); + if (!dev->stat_wq) { + ieee80211_free_hw(hw); + return NULL; + } + + return dev; +} + +#define CHAN2G(_idx, _freq) { \ + .band = IEEE80211_BAND_2GHZ, \ + .center_freq = (_freq), \ + .hw_value = (_idx), \ + .max_power = 30, \ +} + +static const struct ieee80211_channel mt76_channels_2ghz[] = { + CHAN2G(1, 2412), + CHAN2G(2, 2417), + CHAN2G(3, 2422), + CHAN2G(4, 2427), + CHAN2G(5, 2432), + CHAN2G(6, 2437), + CHAN2G(7, 2442), + CHAN2G(8, 2447), + CHAN2G(9, 2452), + CHAN2G(10, 2457), + CHAN2G(11, 2462), + CHAN2G(12, 2467), + CHAN2G(13, 2472), + CHAN2G(14, 2484), +}; + +#define CCK_RATE(_idx, _rate) { \ + .bitrate = _rate, \ + .flags = IEEE80211_RATE_SHORT_PREAMBLE, \ + .hw_value = (MT_PHY_TYPE_CCK << 8) | _idx, \ + .hw_value_short = (MT_PHY_TYPE_CCK << 8) | (8 + _idx), \ +} + +#define OFDM_RATE(_idx, _rate) { \ + .bitrate = _rate, \ + .hw_value = (MT_PHY_TYPE_OFDM << 8) | _idx, \ + .hw_value_short = (MT_PHY_TYPE_OFDM << 8) | _idx, \ +} + +static struct ieee80211_rate mt76_rates[] = { + CCK_RATE(0, 10), + CCK_RATE(1, 20), + CCK_RATE(2, 55), + CCK_RATE(3, 110), + OFDM_RATE(0, 60), + OFDM_RATE(1, 90), + OFDM_RATE(2, 120), + OFDM_RATE(3, 180), + OFDM_RATE(4, 240), + OFDM_RATE(5, 360), + OFDM_RATE(6, 480), + OFDM_RATE(7, 540), +}; + +static int +mt76_init_sband(struct mt7601u_dev *dev, struct ieee80211_supported_band *sband, + const struct ieee80211_channel *chan, int n_chan, + struct ieee80211_rate *rates, int n_rates) +{ + struct ieee80211_sta_ht_cap *ht_cap; + void *chanlist; + int size; + + size = n_chan * sizeof(*chan); + chanlist = devm_kmemdup(dev->dev, chan, size, GFP_KERNEL); + if (!chanlist) + return -ENOMEM; + + sband->channels = chanlist; + sband->n_channels = n_chan; + sband->bitrates = rates; + sband->n_bitrates = n_rates; + + ht_cap = &sband->ht_cap; + ht_cap->ht_supported = true; + ht_cap->cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 | + IEEE80211_HT_CAP_GRN_FLD | + IEEE80211_HT_CAP_SGI_20 | + IEEE80211_HT_CAP_SGI_40 | + (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); + + ht_cap->mcs.rx_mask[0] = 0xff; + ht_cap->mcs.rx_mask[4] = 0x1; + ht_cap->mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; + ht_cap->ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; + ht_cap->ampdu_density = IEEE80211_HT_MPDU_DENSITY_2; + + dev->chandef.chan = &sband->channels[0]; + + return 0; +} + +static int +mt76_init_sband_2g(struct mt7601u_dev *dev) +{ + dev->sband_2g = devm_kzalloc(dev->dev, sizeof(*dev->sband_2g), + GFP_KERNEL); + dev->hw->wiphy->bands[IEEE80211_BAND_2GHZ] = dev->sband_2g; + + WARN_ON(dev->ee->reg.start - 1 + dev->ee->reg.num > + ARRAY_SIZE(mt76_channels_2ghz)); + + return mt76_init_sband(dev, dev->sband_2g, + &mt76_channels_2ghz[dev->ee->reg.start - 1], + dev->ee->reg.num, + mt76_rates, ARRAY_SIZE(mt76_rates)); +} + +int mt7601u_register_device(struct mt7601u_dev *dev) +{ + struct ieee80211_hw *hw = dev->hw; + struct wiphy *wiphy = hw->wiphy; + int ret; + + /* Reserve WCID 0 for mcast - thanks to this APs WCID will go to + * entry no. 1 like it does in the vendor driver. + */ + dev->wcid_mask[0] |= 1; + + /* init fake wcid for monitor interfaces */ + dev->mon_wcid = devm_kmalloc(dev->dev, sizeof(*dev->mon_wcid), + GFP_KERNEL); + if (!dev->mon_wcid) + return -ENOMEM; + dev->mon_wcid->idx = 0xff; + dev->mon_wcid->hw_key_idx = -1; + + SET_IEEE80211_DEV(hw, dev->dev); + + hw->queues = 4; + hw->flags = IEEE80211_HW_SIGNAL_DBM | + IEEE80211_HW_PS_NULLFUNC_STACK | + IEEE80211_HW_SUPPORTS_HT_CCK_RATES | + IEEE80211_HW_AMPDU_AGGREGATION | + IEEE80211_HW_SUPPORTS_RC_TABLE; + hw->max_rates = 1; + hw->max_report_rates = 7; + hw->max_rate_tries = 1; + + hw->sta_data_size = sizeof(struct mt76_sta); + hw->vif_data_size = sizeof(struct mt76_vif); + + SET_IEEE80211_PERM_ADDR(hw, dev->macaddr); + + wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR; + wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION); + + ret = mt76_init_sband_2g(dev); + if (ret) + return ret; + + INIT_DELAYED_WORK(&dev->mac_work, mt7601u_mac_work); + INIT_DELAYED_WORK(&dev->stat_work, mt7601u_tx_stat); + + ret = ieee80211_register_hw(hw); + if (ret) + return ret; + + mt7601u_init_debugfs(dev); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/initvals.h b/drivers/net/wireless/mediatek/mt7601u/initvals.h new file mode 100644 index 0000000000000..ec11ff66969d4 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/initvals.h @@ -0,0 +1,164 @@ +/* + * (c) Copyright 2002-2010, Ralink Technology, Inc. + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_INITVALS_H +#define __MT7601U_INITVALS_H + +static const struct mt76_reg_pair bbp_common_vals[] = { + { 65, 0x2c }, + { 66, 0x38 }, + { 68, 0x0b }, + { 69, 0x12 }, + { 70, 0x0a }, + { 73, 0x10 }, + { 81, 0x37 }, + { 82, 0x62 }, + { 83, 0x6a }, + { 84, 0x99 }, + { 86, 0x00 }, + { 91, 0x04 }, + { 92, 0x00 }, + { 103, 0x00 }, + { 105, 0x05 }, + { 106, 0x35 }, +}; + +static const struct mt76_reg_pair bbp_chip_vals[] = { + { 1, 0x04 }, { 4, 0x40 }, { 20, 0x06 }, { 31, 0x08 }, + /* CCK Tx Control */ + { 178, 0xff }, + /* AGC/Sync controls */ + { 66, 0x14 }, { 68, 0x8b }, { 69, 0x12 }, { 70, 0x09 }, + { 73, 0x11 }, { 75, 0x60 }, { 76, 0x44 }, { 84, 0x9a }, + { 86, 0x38 }, { 91, 0x07 }, { 92, 0x02 }, + /* Rx Path Controls */ + { 99, 0x50 }, { 101, 0x00 }, { 103, 0xc0 }, { 104, 0x92 }, + { 105, 0x3c }, { 106, 0x03 }, { 128, 0x12 }, + /* Change RXWI content: Gain Report */ + { 142, 0x04 }, { 143, 0x37 }, + /* Change RXWI content: Antenna Report */ + { 142, 0x03 }, { 143, 0x99 }, + /* Calibration Index Register */ + /* CCK Receiver Control */ + { 160, 0xeb }, { 161, 0xc4 }, { 162, 0x77 }, { 163, 0xf9 }, + { 164, 0x88 }, { 165, 0x80 }, { 166, 0xff }, { 167, 0xe4 }, + /* Added AGC controls - these AGC/GLRT registers are accessed + * through R195 and R196. + */ + { 195, 0x00 }, { 196, 0x00 }, + { 195, 0x01 }, { 196, 0x04 }, + { 195, 0x02 }, { 196, 0x20 }, + { 195, 0x03 }, { 196, 0x0a }, + { 195, 0x06 }, { 196, 0x16 }, + { 195, 0x07 }, { 196, 0x05 }, + { 195, 0x08 }, { 196, 0x37 }, + { 195, 0x0a }, { 196, 0x15 }, + { 195, 0x0b }, { 196, 0x17 }, + { 195, 0x0c }, { 196, 0x06 }, + { 195, 0x0d }, { 196, 0x09 }, + { 195, 0x0e }, { 196, 0x05 }, + { 195, 0x0f }, { 196, 0x09 }, + { 195, 0x10 }, { 196, 0x20 }, + { 195, 0x20 }, { 196, 0x17 }, + { 195, 0x21 }, { 196, 0x06 }, + { 195, 0x22 }, { 196, 0x09 }, + { 195, 0x23 }, { 196, 0x17 }, + { 195, 0x24 }, { 196, 0x06 }, + { 195, 0x25 }, { 196, 0x09 }, + { 195, 0x26 }, { 196, 0x17 }, + { 195, 0x27 }, { 196, 0x06 }, + { 195, 0x28 }, { 196, 0x09 }, + { 195, 0x29 }, { 196, 0x05 }, + { 195, 0x2a }, { 196, 0x09 }, + { 195, 0x80 }, { 196, 0x8b }, + { 195, 0x81 }, { 196, 0x12 }, + { 195, 0x82 }, { 196, 0x09 }, + { 195, 0x83 }, { 196, 0x17 }, + { 195, 0x84 }, { 196, 0x11 }, + { 195, 0x85 }, { 196, 0x00 }, + { 195, 0x86 }, { 196, 0x00 }, + { 195, 0x87 }, { 196, 0x18 }, + { 195, 0x88 }, { 196, 0x60 }, + { 195, 0x89 }, { 196, 0x44 }, + { 195, 0x8a }, { 196, 0x8b }, + { 195, 0x8b }, { 196, 0x8b }, + { 195, 0x8c }, { 196, 0x8b }, + { 195, 0x8d }, { 196, 0x8b }, + { 195, 0x8e }, { 196, 0x09 }, + { 195, 0x8f }, { 196, 0x09 }, + { 195, 0x90 }, { 196, 0x09 }, + { 195, 0x91 }, { 196, 0x09 }, + { 195, 0x92 }, { 196, 0x11 }, + { 195, 0x93 }, { 196, 0x11 }, + { 195, 0x94 }, { 196, 0x11 }, + { 195, 0x95 }, { 196, 0x11 }, + /* PPAD */ + { 47, 0x80 }, { 60, 0x80 }, { 150, 0xd2 }, { 151, 0x32 }, + { 152, 0x23 }, { 153, 0x41 }, { 154, 0x00 }, { 155, 0x4f }, + { 253, 0x7e }, { 195, 0x30 }, { 196, 0x32 }, { 195, 0x31 }, + { 196, 0x23 }, { 195, 0x32 }, { 196, 0x45 }, { 195, 0x35 }, + { 196, 0x4a }, { 195, 0x36 }, { 196, 0x5a }, { 195, 0x37 }, + { 196, 0x5a }, +}; + +static const struct mt76_reg_pair mac_common_vals[] = { + { MT_LEGACY_BASIC_RATE, 0x0000013f }, + { MT_HT_BASIC_RATE, 0x00008003 }, + { MT_MAC_SYS_CTRL, 0x00000000 }, + { MT_RX_FILTR_CFG, 0x00017f97 }, + { MT_BKOFF_SLOT_CFG, 0x00000209 }, + { MT_TX_SW_CFG0, 0x00000000 }, + { MT_TX_SW_CFG1, 0x00080606 }, + { MT_TX_LINK_CFG, 0x00001020 }, + { MT_TX_TIMEOUT_CFG, 0x000a2090 }, + { MT_MAX_LEN_CFG, 0x00003fff }, + { MT_PBF_TX_MAX_PCNT, 0x1fbf1f1f }, + { MT_PBF_RX_MAX_PCNT, 0x0000009f }, + { MT_TX_RETRY_CFG, 0x47d01f0f }, + { MT_AUTO_RSP_CFG, 0x00000013 }, + { MT_CCK_PROT_CFG, 0x05740003 }, + { MT_OFDM_PROT_CFG, 0x05740003 }, + { MT_MM40_PROT_CFG, 0x03f44084 }, + { MT_GF20_PROT_CFG, 0x01744004 }, + { MT_GF40_PROT_CFG, 0x03f44084 }, + { MT_MM20_PROT_CFG, 0x01744004 }, + { MT_TXOP_CTRL_CFG, 0x0000583f }, + { MT_TX_RTS_CFG, 0x01092b20 }, + { MT_EXP_ACK_TIME, 0x002400ca }, + { MT_TXOP_HLDR_ET, 0x00000002 }, + { MT_XIFS_TIME_CFG, 0x33a41010 }, + { MT_PWR_PIN_CFG, 0x00000000 }, +}; + +static const struct mt76_reg_pair mac_chip_vals[] = { + { MT_TSO_CTRL, 0x00006050 }, + { MT_BCN_OFFSET(0), 0x18100800 }, + { MT_BCN_OFFSET(1), 0x38302820 }, + { MT_PBF_SYS_CTRL, 0x00080c00 }, + { MT_PBF_CFG, 0x7f723c1f }, + { MT_FCE_PSE_CTRL, 0x00000001 }, + { MT_PAUSE_ENABLE_CONTROL1, 0x00000000 }, + { MT_TX0_RF_GAIN_CORR, 0x003b0005 }, + { MT_TX0_RF_GAIN_ATTEN, 0x00006900 }, + { MT_TX0_BB_GAIN_ATTEN, 0x00000400 }, + { MT_TX_ALC_VGA3, 0x00060006 }, + { MT_TX_SW_CFG0, 0x00000402 }, + { MT_TX_SW_CFG1, 0x00000000 }, + { MT_TX_SW_CFG2, 0x00000000 }, + { MT_HEADER_TRANS_CTRL_REG, 0x00000000 }, + { MT_FCE_CSO, 0x0000030f }, + { MT_FCE_PARAMETERS, 0x00256f0f }, +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/initvals_phy.h b/drivers/net/wireless/mediatek/mt7601u/initvals_phy.h new file mode 100644 index 0000000000000..a2bdc3e322bfc --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/initvals_phy.h @@ -0,0 +1,291 @@ +/* + * (c) Copyright 2002-2010, Ralink Technology, Inc. + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_PHY_INITVALS_H +#define __MT7601U_PHY_INITVALS_H + +#define RF_REG_PAIR(bank, reg, value) \ + { MT_MCU_MEMMAP_RF | (bank) << 16 | (reg), value } + +static const struct mt76_reg_pair rf_central[] = { + /* Bank 0 - for central blocks: BG, PLL, XTAL, LO, ADC/DAC */ + RF_REG_PAIR(0, 0, 0x02), + RF_REG_PAIR(0, 1, 0x01), + RF_REG_PAIR(0, 2, 0x11), + RF_REG_PAIR(0, 3, 0xff), + RF_REG_PAIR(0, 4, 0x0a), + RF_REG_PAIR(0, 5, 0x20), + RF_REG_PAIR(0, 6, 0x00), + /* B/G */ + RF_REG_PAIR(0, 7, 0x00), + RF_REG_PAIR(0, 8, 0x00), + RF_REG_PAIR(0, 9, 0x00), + RF_REG_PAIR(0, 10, 0x00), + RF_REG_PAIR(0, 11, 0x21), + /* XO */ + RF_REG_PAIR(0, 13, 0x00), /* 40mhz xtal */ + /* RF_REG_PAIR(0, 13, 0x13), */ /* 20mhz xtal */ + RF_REG_PAIR(0, 14, 0x7c), + RF_REG_PAIR(0, 15, 0x22), + RF_REG_PAIR(0, 16, 0x80), + /* PLL */ + RF_REG_PAIR(0, 17, 0x99), + RF_REG_PAIR(0, 18, 0x99), + RF_REG_PAIR(0, 19, 0x09), + RF_REG_PAIR(0, 20, 0x50), + RF_REG_PAIR(0, 21, 0xb0), + RF_REG_PAIR(0, 22, 0x00), + RF_REG_PAIR(0, 23, 0xc5), + RF_REG_PAIR(0, 24, 0xfc), + RF_REG_PAIR(0, 25, 0x40), + RF_REG_PAIR(0, 26, 0x4d), + RF_REG_PAIR(0, 27, 0x02), + RF_REG_PAIR(0, 28, 0x72), + RF_REG_PAIR(0, 29, 0x01), + RF_REG_PAIR(0, 30, 0x00), + RF_REG_PAIR(0, 31, 0x00), + /* test ports */ + RF_REG_PAIR(0, 32, 0x00), + RF_REG_PAIR(0, 33, 0x00), + RF_REG_PAIR(0, 34, 0x23), + RF_REG_PAIR(0, 35, 0x01), /* change setting to reduce spurs */ + RF_REG_PAIR(0, 36, 0x00), + RF_REG_PAIR(0, 37, 0x00), + /* ADC/DAC */ + RF_REG_PAIR(0, 38, 0x00), + RF_REG_PAIR(0, 39, 0x20), + RF_REG_PAIR(0, 40, 0x00), + RF_REG_PAIR(0, 41, 0xd0), + RF_REG_PAIR(0, 42, 0x1b), + RF_REG_PAIR(0, 43, 0x02), + RF_REG_PAIR(0, 44, 0x00), +}; + +static const struct mt76_reg_pair rf_channel[] = { + RF_REG_PAIR(4, 0, 0x01), + RF_REG_PAIR(4, 1, 0x00), + RF_REG_PAIR(4, 2, 0x00), + RF_REG_PAIR(4, 3, 0x00), + /* LDO */ + RF_REG_PAIR(4, 4, 0x00), + RF_REG_PAIR(4, 5, 0x08), + RF_REG_PAIR(4, 6, 0x00), + /* RX */ + RF_REG_PAIR(4, 7, 0x5b), + RF_REG_PAIR(4, 8, 0x52), + RF_REG_PAIR(4, 9, 0xb6), + RF_REG_PAIR(4, 10, 0x57), + RF_REG_PAIR(4, 11, 0x33), + RF_REG_PAIR(4, 12, 0x22), + RF_REG_PAIR(4, 13, 0x3d), + RF_REG_PAIR(4, 14, 0x3e), + RF_REG_PAIR(4, 15, 0x13), + RF_REG_PAIR(4, 16, 0x22), + RF_REG_PAIR(4, 17, 0x23), + RF_REG_PAIR(4, 18, 0x02), + RF_REG_PAIR(4, 19, 0xa4), + RF_REG_PAIR(4, 20, 0x01), + RF_REG_PAIR(4, 21, 0x12), + RF_REG_PAIR(4, 22, 0x80), + RF_REG_PAIR(4, 23, 0xb3), + RF_REG_PAIR(4, 24, 0x00), /* reserved */ + RF_REG_PAIR(4, 25, 0x00), /* reserved */ + RF_REG_PAIR(4, 26, 0x00), /* reserved */ + RF_REG_PAIR(4, 27, 0x00), /* reserved */ + /* LOGEN */ + RF_REG_PAIR(4, 28, 0x18), + RF_REG_PAIR(4, 29, 0xee), + RF_REG_PAIR(4, 30, 0x6b), + RF_REG_PAIR(4, 31, 0x31), + RF_REG_PAIR(4, 32, 0x5d), + RF_REG_PAIR(4, 33, 0x00), /* reserved */ + /* TX */ + RF_REG_PAIR(4, 34, 0x96), + RF_REG_PAIR(4, 35, 0x55), + RF_REG_PAIR(4, 36, 0x08), + RF_REG_PAIR(4, 37, 0xbb), + RF_REG_PAIR(4, 38, 0xb3), + RF_REG_PAIR(4, 39, 0xb3), + RF_REG_PAIR(4, 40, 0x03), + RF_REG_PAIR(4, 41, 0x00), /* reserved */ + RF_REG_PAIR(4, 42, 0x00), /* reserved */ + RF_REG_PAIR(4, 43, 0xc5), + RF_REG_PAIR(4, 44, 0xc5), + RF_REG_PAIR(4, 45, 0xc5), + RF_REG_PAIR(4, 46, 0x07), + RF_REG_PAIR(4, 47, 0xa8), + RF_REG_PAIR(4, 48, 0xef), + RF_REG_PAIR(4, 49, 0x1a), + /* PA */ + RF_REG_PAIR(4, 54, 0x07), + RF_REG_PAIR(4, 55, 0xa7), + RF_REG_PAIR(4, 56, 0xcc), + RF_REG_PAIR(4, 57, 0x14), + RF_REG_PAIR(4, 58, 0x07), + RF_REG_PAIR(4, 59, 0xa8), + RF_REG_PAIR(4, 60, 0xd7), + RF_REG_PAIR(4, 61, 0x10), + RF_REG_PAIR(4, 62, 0x1c), + RF_REG_PAIR(4, 63, 0x00), /* reserved */ +}; + +static const struct mt76_reg_pair rf_vga[] = { + RF_REG_PAIR(5, 0, 0x47), + RF_REG_PAIR(5, 1, 0x00), + RF_REG_PAIR(5, 2, 0x00), + RF_REG_PAIR(5, 3, 0x08), + RF_REG_PAIR(5, 4, 0x04), + RF_REG_PAIR(5, 5, 0x20), + RF_REG_PAIR(5, 6, 0x3a), + RF_REG_PAIR(5, 7, 0x3a), + RF_REG_PAIR(5, 8, 0x00), + RF_REG_PAIR(5, 9, 0x00), + RF_REG_PAIR(5, 10, 0x10), + RF_REG_PAIR(5, 11, 0x10), + RF_REG_PAIR(5, 12, 0x10), + RF_REG_PAIR(5, 13, 0x10), + RF_REG_PAIR(5, 14, 0x10), + RF_REG_PAIR(5, 15, 0x20), + RF_REG_PAIR(5, 16, 0x22), + RF_REG_PAIR(5, 17, 0x7c), + RF_REG_PAIR(5, 18, 0x00), + RF_REG_PAIR(5, 19, 0x00), + RF_REG_PAIR(5, 20, 0x00), + RF_REG_PAIR(5, 21, 0xf1), + RF_REG_PAIR(5, 22, 0x11), + RF_REG_PAIR(5, 23, 0x02), + RF_REG_PAIR(5, 24, 0x41), + RF_REG_PAIR(5, 25, 0x20), + RF_REG_PAIR(5, 26, 0x00), + RF_REG_PAIR(5, 27, 0xd7), + RF_REG_PAIR(5, 28, 0xa2), + RF_REG_PAIR(5, 29, 0x20), + RF_REG_PAIR(5, 30, 0x49), + RF_REG_PAIR(5, 31, 0x20), + RF_REG_PAIR(5, 32, 0x04), + RF_REG_PAIR(5, 33, 0xf1), + RF_REG_PAIR(5, 34, 0xa1), + RF_REG_PAIR(5, 35, 0x01), + RF_REG_PAIR(5, 41, 0x00), + RF_REG_PAIR(5, 42, 0x00), + RF_REG_PAIR(5, 43, 0x00), + RF_REG_PAIR(5, 44, 0x00), + RF_REG_PAIR(5, 45, 0x00), + RF_REG_PAIR(5, 46, 0x00), + RF_REG_PAIR(5, 47, 0x00), + RF_REG_PAIR(5, 48, 0x00), + RF_REG_PAIR(5, 49, 0x00), + RF_REG_PAIR(5, 50, 0x00), + RF_REG_PAIR(5, 51, 0x00), + RF_REG_PAIR(5, 52, 0x00), + RF_REG_PAIR(5, 53, 0x00), + RF_REG_PAIR(5, 54, 0x00), + RF_REG_PAIR(5, 55, 0x00), + RF_REG_PAIR(5, 56, 0x00), + RF_REG_PAIR(5, 57, 0x00), + RF_REG_PAIR(5, 58, 0x31), + RF_REG_PAIR(5, 59, 0x31), + RF_REG_PAIR(5, 60, 0x0a), + RF_REG_PAIR(5, 61, 0x02), + RF_REG_PAIR(5, 62, 0x00), + RF_REG_PAIR(5, 63, 0x00), +}; + +/* TODO: BBP178 is set to 0xff for "CCK CH14 OBW" which overrides the settings + * from channel switching. Seems stupid at best. + */ +static const struct mt76_reg_pair bbp_high_temp[] = { + { 75, 0x60 }, + { 92, 0x02 }, + { 178, 0xff }, /* For CCK CH14 OBW */ + { 195, 0x88 }, { 196, 0x60 }, +}, bbp_high_temp_bw20[] = { + { 69, 0x12 }, + { 91, 0x07 }, + { 195, 0x23 }, { 196, 0x17 }, + { 195, 0x24 }, { 196, 0x06 }, + { 195, 0x81 }, { 196, 0x12 }, + { 195, 0x83 }, { 196, 0x17 }, +}, bbp_high_temp_bw40[] = { + { 69, 0x15 }, + { 91, 0x04 }, + { 195, 0x23 }, { 196, 0x12 }, + { 195, 0x24 }, { 196, 0x08 }, + { 195, 0x81 }, { 196, 0x15 }, + { 195, 0x83 }, { 196, 0x16 }, +}, bbp_low_temp[] = { + { 178, 0xff }, /* For CCK CH14 OBW */ +}, bbp_low_temp_bw20[] = { + { 69, 0x12 }, + { 75, 0x5e }, + { 91, 0x07 }, + { 92, 0x02 }, + { 195, 0x23 }, { 196, 0x17 }, + { 195, 0x24 }, { 196, 0x06 }, + { 195, 0x81 }, { 196, 0x12 }, + { 195, 0x83 }, { 196, 0x17 }, + { 195, 0x88 }, { 196, 0x5e }, +}, bbp_low_temp_bw40[] = { + { 69, 0x15 }, + { 75, 0x5c }, + { 91, 0x04 }, + { 92, 0x03 }, + { 195, 0x23 }, { 196, 0x10 }, + { 195, 0x24 }, { 196, 0x08 }, + { 195, 0x81 }, { 196, 0x15 }, + { 195, 0x83 }, { 196, 0x16 }, + { 195, 0x88 }, { 196, 0x5b }, +}, bbp_normal_temp[] = { + { 75, 0x60 }, + { 92, 0x02 }, + { 178, 0xff }, /* For CCK CH14 OBW */ + { 195, 0x88 }, { 196, 0x60 }, +}, bbp_normal_temp_bw20[] = { + { 69, 0x12 }, + { 91, 0x07 }, + { 195, 0x23 }, { 196, 0x17 }, + { 195, 0x24 }, { 196, 0x06 }, + { 195, 0x81 }, { 196, 0x12 }, + { 195, 0x83 }, { 196, 0x17 }, +}, bbp_normal_temp_bw40[] = { + { 69, 0x15 }, + { 91, 0x04 }, + { 195, 0x23 }, { 196, 0x12 }, + { 195, 0x24 }, { 196, 0x08 }, + { 195, 0x81 }, { 196, 0x15 }, + { 195, 0x83 }, { 196, 0x16 }, +}; + +#define BBP_TABLE(arr) { arr, ARRAY_SIZE(arr), } + +static const struct reg_table { + const struct mt76_reg_pair *regs; + size_t n; +} bbp_mode_table[3][3] = { + { + BBP_TABLE(bbp_normal_temp_bw20), + BBP_TABLE(bbp_normal_temp_bw40), + BBP_TABLE(bbp_normal_temp), + }, { + BBP_TABLE(bbp_high_temp_bw20), + BBP_TABLE(bbp_high_temp_bw40), + BBP_TABLE(bbp_high_temp), + }, { + BBP_TABLE(bbp_low_temp_bw20), + BBP_TABLE(bbp_low_temp_bw40), + BBP_TABLE(bbp_low_temp), + } +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/mac.c b/drivers/net/wireless/mediatek/mt7601u/mac.c new file mode 100644 index 0000000000000..7514bce1ac91d --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/mac.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "trace.h" +#include + +static void +mt76_mac_process_tx_rate(struct ieee80211_tx_rate *txrate, u16 rate) +{ + u8 idx = MT76_GET(MT_TXWI_RATE_MCS, rate); + + txrate->idx = 0; + txrate->flags = 0; + txrate->count = 1; + + switch (MT76_GET(MT_TXWI_RATE_PHY_MODE, rate)) { + case MT_PHY_TYPE_OFDM: + txrate->idx = idx + 4; + return; + case MT_PHY_TYPE_CCK: + if (idx >= 8) + idx -= 8; + + txrate->idx = idx; + return; + case MT_PHY_TYPE_HT_GF: + txrate->flags |= IEEE80211_TX_RC_GREEN_FIELD; + /* fall through */ + case MT_PHY_TYPE_HT: + txrate->flags |= IEEE80211_TX_RC_MCS; + txrate->idx = idx; + break; + default: + WARN_ON(1); + return; + } + + if (MT76_GET(MT_TXWI_RATE_BW, rate) == MT_PHY_BW_40) + txrate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; + + if (rate & MT_TXWI_RATE_SGI) + txrate->flags |= IEEE80211_TX_RC_SHORT_GI; +} + +static void +mt76_mac_fill_tx_status(struct mt7601u_dev *dev, struct ieee80211_tx_info *info, + struct mt76_tx_status *st) +{ + struct ieee80211_tx_rate *rate = info->status.rates; + int cur_idx, last_rate; + int i; + + last_rate = min_t(int, st->retry, IEEE80211_TX_MAX_RATES - 1); + mt76_mac_process_tx_rate(&rate[last_rate], st->rate); + if (last_rate < IEEE80211_TX_MAX_RATES - 1) + rate[last_rate + 1].idx = -1; + + cur_idx = rate[last_rate].idx + st->retry; + for (i = 0; i <= last_rate; i++) { + rate[i].flags = rate[last_rate].flags; + rate[i].idx = max_t(int, 0, cur_idx - i); + rate[i].count = 1; + } + + if (last_rate > 0) + rate[last_rate - 1].count = st->retry + 1 - last_rate; + + info->status.ampdu_len = 1; + info->status.ampdu_ack_len = st->success; + + if (st->is_probe) + info->flags |= IEEE80211_TX_CTL_RATE_CTRL_PROBE; + + if (st->aggr) + info->flags |= IEEE80211_TX_CTL_AMPDU | + IEEE80211_TX_STAT_AMPDU; + + if (!st->ack_req) + info->flags |= IEEE80211_TX_CTL_NO_ACK; + else if (st->success) + info->flags |= IEEE80211_TX_STAT_ACK; +} + +u16 mt76_mac_tx_rate_val(struct mt7601u_dev *dev, + const struct ieee80211_tx_rate *rate, u8 *nss_val) +{ + u16 rateval; + u8 phy, rate_idx; + u8 nss = 1; + u8 bw = 0; + + if (rate->flags & IEEE80211_TX_RC_MCS) { + rate_idx = rate->idx; + nss = 1 + (rate->idx >> 3); + phy = MT_PHY_TYPE_HT; + if (rate->flags & IEEE80211_TX_RC_GREEN_FIELD) + phy = MT_PHY_TYPE_HT_GF; + if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH) + bw = 1; + } else { + const struct ieee80211_rate *r; + int band = dev->chandef.chan->band; + u16 val; + + r = &dev->hw->wiphy->bands[band]->bitrates[rate->idx]; + if (rate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE) + val = r->hw_value_short; + else + val = r->hw_value; + + phy = val >> 8; + rate_idx = val & 0xff; + bw = 0; + } + + rateval = MT76_SET(MT_RXWI_RATE_MCS, rate_idx); + rateval |= MT76_SET(MT_RXWI_RATE_PHY, phy); + rateval |= MT76_SET(MT_RXWI_RATE_BW, bw); + if (rate->flags & IEEE80211_TX_RC_SHORT_GI) + rateval |= MT_RXWI_RATE_SGI; + + *nss_val = nss; + return rateval; +} + +void mt76_mac_wcid_set_rate(struct mt7601u_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + wcid->tx_rate = mt76_mac_tx_rate_val(dev, rate, &wcid->tx_rate_nss); + wcid->tx_rate_set = true; + spin_unlock_irqrestore(&dev->lock, flags); +} + +struct mt76_tx_status mt7601u_mac_fetch_tx_status(struct mt7601u_dev *dev) +{ + struct mt76_tx_status stat = {}; + u32 val; + + val = mt7601u_rr(dev, MT_TX_STAT_FIFO); + stat.valid = !!(val & MT_TX_STAT_FIFO_VALID); + stat.success = !!(val & MT_TX_STAT_FIFO_SUCCESS); + stat.aggr = !!(val & MT_TX_STAT_FIFO_AGGR); + stat.ack_req = !!(val & MT_TX_STAT_FIFO_ACKREQ); + stat.pktid = MT76_GET(MT_TX_STAT_FIFO_PID_TYPE, val); + stat.wcid = MT76_GET(MT_TX_STAT_FIFO_WCID, val); + stat.rate = MT76_GET(MT_TX_STAT_FIFO_RATE, val); + + return stat; +} + +void mt76_send_tx_status(struct mt7601u_dev *dev, struct mt76_tx_status *stat) +{ + struct ieee80211_tx_info info = {}; + struct ieee80211_sta *sta = NULL; + struct mt76_wcid *wcid = NULL; + void *msta; + + rcu_read_lock(); + if (stat->wcid < ARRAY_SIZE(dev->wcid)) + wcid = rcu_dereference(dev->wcid[stat->wcid]); + + if (wcid) { + msta = container_of(wcid, struct mt76_sta, wcid); + sta = container_of(msta, struct ieee80211_sta, + drv_priv); + } + + mt76_mac_fill_tx_status(dev, &info, stat); + ieee80211_tx_status_noskb(dev->hw, sta, &info); + rcu_read_unlock(); +} + +void mt7601u_mac_set_protection(struct mt7601u_dev *dev, bool legacy_prot, + int ht_mode) +{ + int mode = ht_mode & IEEE80211_HT_OP_MODE_PROTECTION; + bool non_gf = !!(ht_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + u32 prot[6]; + bool ht_rts[4] = {}; + int i; + + prot[0] = MT_PROT_NAV_SHORT | + MT_PROT_TXOP_ALLOW_ALL | + MT_PROT_RTS_THR_EN; + prot[1] = prot[0]; + if (legacy_prot) + prot[1] |= MT_PROT_CTRL_CTS2SELF; + + prot[2] = prot[4] = MT_PROT_NAV_SHORT | MT_PROT_TXOP_ALLOW_BW20; + prot[3] = prot[5] = MT_PROT_NAV_SHORT | MT_PROT_TXOP_ALLOW_ALL; + + if (legacy_prot) { + prot[2] |= MT_PROT_RATE_CCK_11; + prot[3] |= MT_PROT_RATE_CCK_11; + prot[4] |= MT_PROT_RATE_CCK_11; + prot[5] |= MT_PROT_RATE_CCK_11; + } else { + prot[2] |= MT_PROT_RATE_OFDM_24; + prot[3] |= MT_PROT_RATE_DUP_OFDM_24; + prot[4] |= MT_PROT_RATE_OFDM_24; + prot[5] |= MT_PROT_RATE_DUP_OFDM_24; + } + + switch (mode) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONE: + break; + + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + ht_rts[0] = ht_rts[1] = ht_rts[2] = ht_rts[3] = true; + break; + + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + ht_rts[1] = ht_rts[3] = true; + break; + + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + ht_rts[0] = ht_rts[1] = ht_rts[2] = ht_rts[3] = true; + break; + } + + if (non_gf) + ht_rts[2] = ht_rts[3] = true; + + for (i = 0; i < 4; i++) + if (ht_rts[i]) + prot[i + 2] |= MT_PROT_CTRL_RTS_CTS; + + for (i = 0; i < 6; i++) + mt7601u_wr(dev, MT_CCK_PROT_CFG + i * 4, prot[i]); +} + +void mt7601u_mac_set_short_preamble(struct mt7601u_dev *dev, bool short_preamb) +{ + if (short_preamb) + mt76_set(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_PREAMB_SHORT); + else + mt76_clear(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_PREAMB_SHORT); +} + +void mt7601u_mac_config_tsf(struct mt7601u_dev *dev, bool enable, int interval) +{ + u32 val = mt7601u_rr(dev, MT_BEACON_TIME_CFG); + + val &= ~(MT_BEACON_TIME_CFG_TIMER_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | + MT_BEACON_TIME_CFG_TBTT_EN); + + if (!enable) { + mt7601u_wr(dev, MT_BEACON_TIME_CFG, val); + return; + } + + val &= ~MT_BEACON_TIME_CFG_INTVAL; + val |= MT76_SET(MT_BEACON_TIME_CFG_INTVAL, interval << 4) | + MT_BEACON_TIME_CFG_TIMER_EN | + MT_BEACON_TIME_CFG_SYNC_MODE | + MT_BEACON_TIME_CFG_TBTT_EN; +} + +static void mt7601u_check_mac_err(struct mt7601u_dev *dev) +{ + u32 val = mt7601u_rr(dev, 0x10f4); + + if (!(val & BIT(29)) || !(val & (BIT(7) | BIT(5)))) + return; + + dev_err(dev->dev, "Error: MAC specific condition occurred\n"); + + mt76_set(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_RESET_CSR); + udelay(10); + mt76_clear(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_RESET_CSR); +} + +void mt7601u_mac_work(struct work_struct *work) +{ + struct mt7601u_dev *dev = container_of(work, struct mt7601u_dev, + mac_work.work); + struct { + u32 addr_base; + u32 span; + u64 *stat_base; + } spans[] = { + { MT_RX_STA_CNT0, 3, dev->stats.rx_stat }, + { MT_TX_STA_CNT0, 3, dev->stats.tx_stat }, + { MT_TX_AGG_STAT, 1, dev->stats.aggr_stat }, + { MT_MPDU_DENSITY_CNT, 1, dev->stats.zero_len_del }, + { MT_TX_AGG_CNT_BASE0, 8, &dev->stats.aggr_n[0] }, + { MT_TX_AGG_CNT_BASE1, 8, &dev->stats.aggr_n[16] }, + }; + u32 sum, n; + int i, j, k; + + /* Note: using MCU_RANDOM_READ is actually slower then reading all the + * registers by hand. MCU takes ca. 20ms to complete read of 24 + * registers while reading them one by one will takes roughly + * 24*200us =~ 5ms. + */ + + k = 0; + n = 0; + sum = 0; + for (i = 0; i < ARRAY_SIZE(spans); i++) + for (j = 0; j < spans[i].span; j++) { + u32 val = mt7601u_rr(dev, spans[i].addr_base + j * 4); + + spans[i].stat_base[j * 2] += val & 0xffff; + spans[i].stat_base[j * 2 + 1] += val >> 16; + + /* Calculate average AMPDU length */ + if (spans[i].addr_base != MT_TX_AGG_CNT_BASE0 && + spans[i].addr_base != MT_TX_AGG_CNT_BASE1) + continue; + + n += (val >> 16) + (val & 0xffff); + sum += (val & 0xffff) * (1 + k * 2) + + (val >> 16) * (2 + k * 2); + k++; + } + + atomic_set(&dev->avg_ampdu_len, n ? DIV_ROUND_CLOSEST(sum, n) : 1); + + mt7601u_check_mac_err(dev); + + ieee80211_queue_delayed_work(dev->hw, &dev->mac_work, 10 * HZ); +} + +void +mt7601u_mac_wcid_setup(struct mt7601u_dev *dev, u8 idx, u8 vif_idx, u8 *mac) +{ + u8 zmac[ETH_ALEN] = {}; + u32 attr; + + attr = MT76_SET(MT_WCID_ATTR_BSS_IDX, vif_idx & 7) | + MT76_SET(MT_WCID_ATTR_BSS_IDX_EXT, !!(vif_idx & 8)); + + mt76_wr(dev, MT_WCID_ATTR(idx), attr); + + if (mac) + memcpy(zmac, mac, sizeof(zmac)); + + mt7601u_addr_wr(dev, MT_WCID_ADDR(idx), zmac); +} + +void mt7601u_mac_set_ampdu_factor(struct mt7601u_dev *dev) +{ + struct ieee80211_sta *sta; + struct mt76_wcid *wcid; + void *msta; + u8 min_factor = 3; + int i; + + rcu_read_lock(); + for (i = 0; i < ARRAY_SIZE(dev->wcid); i++) { + wcid = rcu_dereference(dev->wcid[i]); + if (!wcid) + continue; + + msta = container_of(wcid, struct mt76_sta, wcid); + sta = container_of(msta, struct ieee80211_sta, drv_priv); + + min_factor = min(min_factor, sta->ht_cap.ampdu_factor); + } + rcu_read_unlock(); + + mt7601u_wr(dev, MT_MAX_LEN_CFG, 0xa0fff | + MT76_SET(MT_MAX_LEN_CFG_AMPDU, min_factor)); +} + +static void +mt76_mac_process_rate(struct ieee80211_rx_status *status, u16 rate) +{ + u8 idx = MT76_GET(MT_RXWI_RATE_MCS, rate); + + switch (MT76_GET(MT_RXWI_RATE_PHY, rate)) { + case MT_PHY_TYPE_OFDM: + if (WARN_ON(idx >= 8)) + idx = 0; + idx += 4; + + status->rate_idx = idx; + return; + case MT_PHY_TYPE_CCK: + if (idx >= 8) { + idx -= 8; + status->flag |= RX_FLAG_SHORTPRE; + } + + if (WARN_ON(idx >= 4)) + idx = 0; + + status->rate_idx = idx; + return; + case MT_PHY_TYPE_HT_GF: + status->flag |= RX_FLAG_HT_GF; + /* fall through */ + case MT_PHY_TYPE_HT: + status->flag |= RX_FLAG_HT; + status->rate_idx = idx; + break; + default: + WARN_ON(1); + return; + } + + if (rate & MT_RXWI_RATE_SGI) + status->flag |= RX_FLAG_SHORT_GI; + + if (rate & MT_RXWI_RATE_STBC) + status->flag |= 1 << RX_FLAG_STBC_SHIFT; + + if (rate & MT_RXWI_RATE_BW) + status->flag |= RX_FLAG_40MHZ; +} + +static void +mt7601u_rx_monitor_beacon(struct mt7601u_dev *dev, struct mt7601u_rxwi *rxwi, + u16 rate, int rssi) +{ + dev->bcn_freq_off = rxwi->freq_off; + dev->bcn_phy_mode = MT76_GET(MT_RXWI_RATE_PHY, rate); + dev->avg_rssi = (dev->avg_rssi * 15) / 16 + (rssi << 8); +} + +static int +mt7601u_rx_is_our_beacon(struct mt7601u_dev *dev, u8 *data) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)data; + + return ieee80211_is_beacon(hdr->frame_control) && + ether_addr_equal(hdr->addr2, dev->ap_bssid); +} + +u32 mt76_mac_process_rx(struct mt7601u_dev *dev, struct sk_buff *skb, + u8 *data, void *rxi) +{ + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); + struct mt7601u_rxwi *rxwi = rxi; + u32 len, ctl = le32_to_cpu(rxwi->ctl); + u16 rate = le16_to_cpu(rxwi->rate); + int rssi; + + len = MT76_GET(MT_RXWI_CTL_MPDU_LEN, ctl); + if (len < 10) + return 0; + + if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_DECRYPT)) { + status->flag |= RX_FLAG_DECRYPTED; + status->flag |= RX_FLAG_IV_STRIPPED | RX_FLAG_MMIC_STRIPPED; + } + + status->chains = BIT(0); + rssi = mt7601u_phy_get_rssi(dev, rxwi, rate); + status->chain_signal[0] = status->signal = rssi; + status->freq = dev->chandef.chan->center_freq; + status->band = dev->chandef.chan->band; + + mt76_mac_process_rate(status, rate); + + spin_lock_bh(&dev->con_mon_lock); + if (mt7601u_rx_is_our_beacon(dev, data)) + mt7601u_rx_monitor_beacon(dev, rxwi, rate, rssi); + else if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_U2M)) + dev->avg_rssi = (dev->avg_rssi * 15) / 16 + (rssi << 8); + spin_unlock_bh(&dev->con_mon_lock); + + return len; +} + +static enum mt76_cipher_type +mt76_mac_get_key_info(struct ieee80211_key_conf *key, u8 *key_data) +{ + memset(key_data, 0, 32); + if (!key) + return MT_CIPHER_NONE; + + if (key->keylen > 32) + return MT_CIPHER_NONE; + + memcpy(key_data, key->key, key->keylen); + + switch (key->cipher) { + case WLAN_CIPHER_SUITE_WEP40: + return MT_CIPHER_WEP40; + case WLAN_CIPHER_SUITE_WEP104: + return MT_CIPHER_WEP104; + case WLAN_CIPHER_SUITE_TKIP: + return MT_CIPHER_TKIP; + case WLAN_CIPHER_SUITE_CCMP: + return MT_CIPHER_AES_CCMP; + default: + return MT_CIPHER_NONE; + } +} + +int mt76_mac_wcid_set_key(struct mt7601u_dev *dev, u8 idx, + struct ieee80211_key_conf *key) +{ + enum mt76_cipher_type cipher; + u8 key_data[32]; + u8 iv_data[8]; + u32 val; + + cipher = mt76_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EINVAL; + + trace_set_key(dev, idx); + + mt7601u_wr_copy(dev, MT_WCID_KEY(idx), key_data, sizeof(key_data)); + + memset(iv_data, 0, sizeof(iv_data)); + if (key) { + iv_data[3] = key->keyidx << 6; + if (cipher >= MT_CIPHER_TKIP) { + /* Note: start with 1 to comply with spec, + * (see comment on common/cmm_wpa.c:4291). + */ + iv_data[0] |= 1; + iv_data[3] |= 0x20; + } + } + mt7601u_wr_copy(dev, MT_WCID_IV(idx), iv_data, sizeof(iv_data)); + + val = mt7601u_rr(dev, MT_WCID_ATTR(idx)); + val &= ~MT_WCID_ATTR_PKEY_MODE & ~MT_WCID_ATTR_PKEY_MODE_EXT; + val |= MT76_SET(MT_WCID_ATTR_PKEY_MODE, cipher & 7) | + MT76_SET(MT_WCID_ATTR_PKEY_MODE_EXT, cipher >> 3); + val &= ~MT_WCID_ATTR_PAIRWISE; + val |= MT_WCID_ATTR_PAIRWISE * + !!(key && key->flags & IEEE80211_KEY_FLAG_PAIRWISE); + mt7601u_wr(dev, MT_WCID_ATTR(idx), val); + + return 0; +} + +int mt76_mac_shared_key_setup(struct mt7601u_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key) +{ + enum mt76_cipher_type cipher; + u8 key_data[32]; + u32 val; + + cipher = mt76_mac_get_key_info(key, key_data); + if (cipher == MT_CIPHER_NONE && key) + return -EINVAL; + + trace_set_shared_key(dev, vif_idx, key_idx); + + mt7601u_wr_copy(dev, MT_SKEY(vif_idx, key_idx), + key_data, sizeof(key_data)); + + val = mt76_rr(dev, MT_SKEY_MODE(vif_idx)); + val &= ~(MT_SKEY_MODE_MASK << MT_SKEY_MODE_SHIFT(vif_idx, key_idx)); + val |= cipher << MT_SKEY_MODE_SHIFT(vif_idx, key_idx); + mt76_wr(dev, MT_SKEY_MODE(vif_idx), val); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/mac.h b/drivers/net/wireless/mediatek/mt7601u/mac.h new file mode 100644 index 0000000000000..2c22d63c63a22 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/mac.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT76_MAC_H +#define __MT76_MAC_H + +struct mt76_tx_status { + u8 valid:1; + u8 success:1; + u8 aggr:1; + u8 ack_req:1; + u8 is_probe:1; + u8 wcid; + u8 pktid; + u8 retry; + u16 rate; +} __packed __aligned(2); + +/* Note: values in original "RSSI" and "SNR" fields are not actually what they + * are called for MT7601U, names used by this driver are educated guesses + * (see vendor mac/ral_omac.c). + */ +struct mt7601u_rxwi { + __le32 rxinfo; + + __le32 ctl; + + __le16 frag_sn; + __le16 rate; + + u8 unknown; + u8 zero[3]; + + u8 snr; + u8 ant; + u8 gain; + u8 freq_off; + + __le32 resv2; + __le32 expert_ant; +} __packed __aligned(4); + +#define MT_RXINFO_BA BIT(0) +#define MT_RXINFO_DATA BIT(1) +#define MT_RXINFO_NULL BIT(2) +#define MT_RXINFO_FRAG BIT(3) +#define MT_RXINFO_U2M BIT(4) +#define MT_RXINFO_MULTICAST BIT(5) +#define MT_RXINFO_BROADCAST BIT(6) +#define MT_RXINFO_MYBSS BIT(7) +#define MT_RXINFO_CRCERR BIT(8) +#define MT_RXINFO_ICVERR BIT(9) +#define MT_RXINFO_MICERR BIT(10) +#define MT_RXINFO_AMSDU BIT(11) +#define MT_RXINFO_HTC BIT(12) +#define MT_RXINFO_RSSI BIT(13) +#define MT_RXINFO_L2PAD BIT(14) +#define MT_RXINFO_AMPDU BIT(15) +#define MT_RXINFO_DECRYPT BIT(16) +#define MT_RXINFO_BSSIDX3 BIT(17) +#define MT_RXINFO_WAPI_KEY BIT(18) +#define MT_RXINFO_PN_LEN GENMASK(21, 19) +#define MT_RXINFO_SW_PKT_80211 BIT(22) +#define MT_RXINFO_TCP_SUM_BYPASS BIT(28) +#define MT_RXINFO_IP_SUM_BYPASS BIT(29) +#define MT_RXINFO_TCP_SUM_ERR BIT(30) +#define MT_RXINFO_IP_SUM_ERR BIT(31) + +#define MT_RXWI_CTL_WCID GENMASK(7, 0) +#define MT_RXWI_CTL_KEY_IDX GENMASK(9, 8) +#define MT_RXWI_CTL_BSS_IDX GENMASK(12, 10) +#define MT_RXWI_CTL_UDF GENMASK(15, 13) +#define MT_RXWI_CTL_MPDU_LEN GENMASK(27, 16) +#define MT_RXWI_CTL_TID GENMASK(31, 28) + +#define MT_RXWI_FRAG GENMASK(3, 0) +#define MT_RXWI_SN GENMASK(15, 4) + +#define MT_RXWI_RATE_MCS GENMASK(6, 0) +#define MT_RXWI_RATE_BW BIT(7) +#define MT_RXWI_RATE_SGI BIT(8) +#define MT_RXWI_RATE_STBC GENMASK(10, 9) +#define MT_RXWI_RATE_ETXBF BIT(11) +#define MT_RXWI_RATE_SND BIT(12) +#define MT_RXWI_RATE_ITXBF BIT(13) +#define MT_RXWI_RATE_PHY GENMASK(15, 14) + +#define MT_RXWI_GAIN_RSSI_VAL GENMASK(5, 0) +#define MT_RXWI_GAIN_RSSI_LNA_ID GENMASK(7, 6) +#define MT_RXWI_ANT_AUX_LNA BIT(7) + +#define MT_RXWI_EANT_ENC_ANT_ID GENMASK(7, 0) + +enum mt76_phy_type { + MT_PHY_TYPE_CCK, + MT_PHY_TYPE_OFDM, + MT_PHY_TYPE_HT, + MT_PHY_TYPE_HT_GF, +}; + +enum mt76_phy_bandwidth { + MT_PHY_BW_20, + MT_PHY_BW_40, +}; + +struct mt76_txwi { + __le16 flags; + __le16 rate_ctl; + + u8 ack_ctl; + u8 wcid; + __le16 len_ctl; + + __le32 iv; + + __le32 eiv; + + u8 aid; + u8 txstream; + __le16 ctl; +} __packed __aligned(4); + +#define MT_TXWI_FLAGS_FRAG BIT(0) +#define MT_TXWI_FLAGS_MMPS BIT(1) +#define MT_TXWI_FLAGS_CFACK BIT(2) +#define MT_TXWI_FLAGS_TS BIT(3) +#define MT_TXWI_FLAGS_AMPDU BIT(4) +#define MT_TXWI_FLAGS_MPDU_DENSITY GENMASK(7, 5) +#define MT_TXWI_FLAGS_TXOP GENMASK(9, 8) +#define MT_TXWI_FLAGS_CWMIN GENMASK(12, 10) +#define MT_TXWI_FLAGS_NO_RATE_FALLBACK BIT(13) +#define MT_TXWI_FLAGS_TX_RPT BIT(14) +#define MT_TXWI_FLAGS_TX_RATE_LUT BIT(15) + +#define MT_TXWI_RATE_MCS GENMASK(6, 0) +#define MT_TXWI_RATE_BW BIT(7) +#define MT_TXWI_RATE_SGI BIT(8) +#define MT_TXWI_RATE_STBC GENMASK(10, 9) +#define MT_TXWI_RATE_PHY_MODE GENMASK(15, 14) + +#define MT_TXWI_ACK_CTL_REQ BIT(0) +#define MT_TXWI_ACK_CTL_NSEQ BIT(1) +#define MT_TXWI_ACK_CTL_BA_WINDOW GENMASK(7, 2) + +#define MT_TXWI_LEN_BYTE_CNT GENMASK(11, 0) +#define MT_TXWI_LEN_PKTID GENMASK(15, 12) + +#define MT_TXWI_CTL_TX_POWER_ADJ GENMASK(3, 0) +#define MT_TXWI_CTL_CHAN_CHECK_PKT BIT(4) +#define MT_TXWI_CTL_PIFS_REV BIT(6) + +u32 mt76_mac_process_rx(struct mt7601u_dev *dev, struct sk_buff *skb, + u8 *data, void *rxi); +int mt76_mac_wcid_set_key(struct mt7601u_dev *dev, u8 idx, + struct ieee80211_key_conf *key); +void mt76_mac_wcid_set_rate(struct mt7601u_dev *dev, struct mt76_wcid *wcid, + const struct ieee80211_tx_rate *rate); + +int mt76_mac_shared_key_setup(struct mt7601u_dev *dev, u8 vif_idx, u8 key_idx, + struct ieee80211_key_conf *key); +u16 mt76_mac_tx_rate_val(struct mt7601u_dev *dev, + const struct ieee80211_tx_rate *rate, u8 *nss_val); +struct mt76_tx_status +mt7601u_mac_fetch_tx_status(struct mt7601u_dev *dev); +void mt76_send_tx_status(struct mt7601u_dev *dev, struct mt76_tx_status *stat); + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/main.c b/drivers/net/wireless/mediatek/mt7601u/main.c new file mode 100644 index 0000000000000..169384b48b27d --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/main.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "mac.h" +#include +#include + +static int mt7601u_start(struct ieee80211_hw *hw) +{ + struct mt7601u_dev *dev = hw->priv; + int ret; + + mutex_lock(&dev->mutex); + + ret = mt7601u_mac_start(dev); + if (ret) + goto out; + + ieee80211_queue_delayed_work(dev->hw, &dev->mac_work, + MT_CALIBRATE_INTERVAL); + ieee80211_queue_delayed_work(dev->hw, &dev->cal_work, + MT_CALIBRATE_INTERVAL); +out: + mutex_unlock(&dev->mutex); + return ret; +} + +static void mt7601u_stop(struct ieee80211_hw *hw) +{ + struct mt7601u_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + + cancel_delayed_work_sync(&dev->cal_work); + cancel_delayed_work_sync(&dev->mac_work); + mt7601u_mac_stop(dev); + + mutex_unlock(&dev->mutex); +} + +static int mt7601u_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + unsigned int idx = 0; + unsigned int wcid = GROUP_WCID(idx); + + /* Note: for AP do the AP-STA things mt76 does: + * - beacon offsets + * - do mac address tricks + * - shift vif idx + */ + mvif->idx = idx; + + if (dev->wcid_mask[wcid / BITS_PER_LONG] & BIT(wcid % BITS_PER_LONG)) + return -ENOSPC; + dev->wcid_mask[wcid / BITS_PER_LONG] |= BIT(wcid % BITS_PER_LONG); + mvif->group_wcid.idx = wcid; + mvif->group_wcid.hw_key_idx = -1; + + return 0; +} + +static void mt7601u_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + unsigned int wcid = mvif->group_wcid.idx; + + dev->wcid_mask[wcid / BITS_PER_LONG] &= ~BIT(wcid % BITS_PER_LONG); +} + +static int mt7601u_config(struct ieee80211_hw *hw, u32 changed) +{ + struct mt7601u_dev *dev = hw->priv; + int ret = 0; + + mutex_lock(&dev->mutex); + + if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { + ieee80211_stop_queues(hw); + ret = mt7601u_phy_set_channel(dev, &hw->conf.chandef); + ieee80211_wake_queues(hw); + } + + mutex_unlock(&dev->mutex); + + return ret; +} + +static void +mt76_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags, + unsigned int *total_flags, u64 multicast) +{ + struct mt7601u_dev *dev = hw->priv; + u32 flags = 0; + +#define MT76_FILTER(_flag, _hw) do { \ + flags |= *total_flags & FIF_##_flag; \ + dev->rxfilter &= ~(_hw); \ + dev->rxfilter |= !(flags & FIF_##_flag) * (_hw); \ + } while (0) + + mutex_lock(&dev->mutex); + + dev->rxfilter &= ~MT_RX_FILTR_CFG_OTHER_BSS; + + MT76_FILTER(OTHER_BSS, MT_RX_FILTR_CFG_PROMISC); + MT76_FILTER(FCSFAIL, MT_RX_FILTR_CFG_CRC_ERR); + MT76_FILTER(PLCPFAIL, MT_RX_FILTR_CFG_PHY_ERR); + MT76_FILTER(CONTROL, MT_RX_FILTR_CFG_ACK | + MT_RX_FILTR_CFG_CTS | + MT_RX_FILTR_CFG_CFEND | + MT_RX_FILTR_CFG_CFACK | + MT_RX_FILTR_CFG_BA | + MT_RX_FILTR_CFG_CTRL_RSV); + MT76_FILTER(PSPOLL, MT_RX_FILTR_CFG_PSPOLL); + + *total_flags = flags; + mt76_wr(dev, MT_RX_FILTR_CFG, dev->rxfilter); + + mutex_unlock(&dev->mutex); +} + +static void +mt7601u_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *info, u32 changed) +{ + struct mt7601u_dev *dev = hw->priv; + + mutex_lock(&dev->mutex); + + if (changed & BSS_CHANGED_ASSOC) + mt7601u_phy_con_cal_onoff(dev, info); + + if (changed & BSS_CHANGED_BSSID) { + mt7601u_addr_wr(dev, MT_MAC_BSSID_DW0, info->bssid); + + /* Note: this is a hack because beacon_int is not changed + * on leave nor is any more appropriate event generated. + * rt2x00 doesn't seem to be bothered though. + */ + if (is_zero_ether_addr(info->bssid)) + mt7601u_mac_config_tsf(dev, false, 0); + } + + if (changed & BSS_CHANGED_BASIC_RATES) { + mt7601u_wr(dev, MT_LEGACY_BASIC_RATE, info->basic_rates); + mt7601u_wr(dev, MT_HT_FBK_CFG0, 0x65432100); + mt7601u_wr(dev, MT_HT_FBK_CFG1, 0xedcba980); + mt7601u_wr(dev, MT_LG_FBK_CFG0, 0xedcba988); + mt7601u_wr(dev, MT_LG_FBK_CFG1, 0x00002100); + } + + if (changed & BSS_CHANGED_BEACON_INT) + mt7601u_mac_config_tsf(dev, true, info->beacon_int); + + if (changed & BSS_CHANGED_HT || changed & BSS_CHANGED_ERP_CTS_PROT) + mt7601u_mac_set_protection(dev, info->use_cts_prot, + info->ht_operation_mode); + + if (changed & BSS_CHANGED_ERP_PREAMBLE) + mt7601u_mac_set_short_preamble(dev, info->use_short_preamble); + + if (changed & BSS_CHANGED_ERP_SLOT) { + int slottime = info->use_short_slot ? 9 : 20; + + mt76_rmw_field(dev, MT_BKOFF_SLOT_CFG, + MT_BKOFF_SLOT_CFG_SLOTTIME, slottime); + } + + if (changed & BSS_CHANGED_ASSOC) + mt7601u_phy_recalibrate_after_assoc(dev); + + mutex_unlock(&dev->mutex); +} + +static int +mt76_wcid_alloc(struct mt7601u_dev *dev) +{ + int i, idx = 0; + + for (i = 0; i < ARRAY_SIZE(dev->wcid_mask); i++) { + idx = ffs(~dev->wcid_mask[i]); + if (!idx) + continue; + + idx--; + dev->wcid_mask[i] |= BIT(idx); + break; + } + + idx = i * BITS_PER_LONG + idx; + if (idx > 119) + return -1; + + return idx; +} + +static int +mt7601u_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + int ret = 0; + int idx = 0; + + mutex_lock(&dev->mutex); + + idx = mt76_wcid_alloc(dev); + if (idx < 0) { + ret = -ENOSPC; + goto out; + } + + msta->wcid.idx = idx; + msta->wcid.hw_key_idx = -1; + mt7601u_mac_wcid_setup(dev, idx, mvif->idx, sta->addr); + mt76_clear(dev, MT_WCID_DROP(idx), MT_WCID_DROP_MASK(idx)); + rcu_assign_pointer(dev->wcid[idx], &msta->wcid); + mt7601u_mac_set_ampdu_factor(dev); + +out: + mutex_unlock(&dev->mutex); + + return ret; +} + +static int +mt7601u_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + int idx = msta->wcid.idx; + + mutex_lock(&dev->mutex); + rcu_assign_pointer(dev->wcid[idx], NULL); + mt76_set(dev, MT_WCID_DROP(idx), MT_WCID_DROP_MASK(idx)); + dev->wcid_mask[idx / BITS_PER_LONG] &= ~BIT(idx % BITS_PER_LONG); + mt7601u_mac_wcid_setup(dev, idx, 0, NULL); + mt7601u_mac_set_ampdu_factor(dev); + mutex_unlock(&dev->mutex); + + return 0; +} + +static void +mt7601u_sta_notify(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum sta_notify_cmd cmd, struct ieee80211_sta *sta) +{ +} + +static void +mt7601u_sw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + const u8 *mac_addr) +{ + struct mt7601u_dev *dev = hw->priv; + + mt7601u_agc_save(dev); + set_bit(MT7601U_STATE_SCANNING, &dev->state); +} + +static void +mt7601u_sw_scan_complete(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mt7601u_dev *dev = hw->priv; + + mt7601u_agc_restore(dev); + clear_bit(MT7601U_STATE_SCANNING, &dev->state); +} + +static int +mt7601u_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, + struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct ieee80211_key_conf *key) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_vif *mvif = (struct mt76_vif *) vif->drv_priv; + struct mt76_sta *msta = sta ? (struct mt76_sta *) sta->drv_priv : NULL; + struct mt76_wcid *wcid = msta ? &msta->wcid : &mvif->group_wcid; + int idx = key->keyidx; + int ret; + + if (cmd == SET_KEY) { + key->hw_key_idx = wcid->idx; + wcid->hw_key_idx = idx; + } else { + if (idx == wcid->hw_key_idx) + wcid->hw_key_idx = -1; + + key = NULL; + } + + if (!msta) { + if (key || wcid->hw_key_idx == idx) { + ret = mt76_mac_wcid_set_key(dev, wcid->idx, key); + if (ret) + return ret; + } + + return mt76_mac_shared_key_setup(dev, mvif->idx, idx, key); + } + + return mt76_mac_wcid_set_key(dev, msta->wcid.idx, key); +} + +static int mt7601u_set_rts_threshold(struct ieee80211_hw *hw, u32 value) +{ + struct mt7601u_dev *dev = hw->priv; + + mt76_rmw_field(dev, MT_TX_RTS_CFG, MT_TX_RTS_CFG_THRESH, value); + + return 0; +} + +static int +mt76_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + enum ieee80211_ampdu_mlme_action action, + struct ieee80211_sta *sta, u16 tid, u16 *ssn, u8 buf_size) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + + WARN_ON(msta->wcid.idx > GROUP_WCID(0)); + + switch (action) { + case IEEE80211_AMPDU_RX_START: + mt76_set(dev, MT_WCID_ADDR(msta->wcid.idx) + 4, BIT(16 + tid)); + break; + case IEEE80211_AMPDU_RX_STOP: + mt76_clear(dev, MT_WCID_ADDR(msta->wcid.idx) + 4, + BIT(16 + tid)); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + ieee80211_send_bar(vif, sta->addr, tid, msta->agg_ssn[tid]); + break; + case IEEE80211_AMPDU_TX_STOP_FLUSH: + case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: + break; + case IEEE80211_AMPDU_TX_START: + msta->agg_ssn[tid] = *ssn << 4; + ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_STOP_CONT: + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + } + + return 0; +} + +static void +mt76_sta_rate_tbl_update(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta) +{ + struct mt7601u_dev *dev = hw->priv; + struct mt76_sta *msta = (struct mt76_sta *) sta->drv_priv; + struct ieee80211_sta_rates *rates; + struct ieee80211_tx_rate rate = {}; + + rcu_read_lock(); + rates = rcu_dereference(sta->rates); + + if (!rates) + goto out; + + rate.idx = rates->rate[0].idx; + rate.flags = rates->rate[0].flags; + mt76_mac_wcid_set_rate(dev, &msta->wcid, &rate); + +out: + rcu_read_unlock(); +} + +const struct ieee80211_ops mt7601u_ops = { + .tx = mt7601u_tx, + .start = mt7601u_start, + .stop = mt7601u_stop, + .add_interface = mt7601u_add_interface, + .remove_interface = mt7601u_remove_interface, + .config = mt7601u_config, + .configure_filter = mt76_configure_filter, + .bss_info_changed = mt7601u_bss_info_changed, + .sta_add = mt7601u_sta_add, + .sta_remove = mt7601u_sta_remove, + .sta_notify = mt7601u_sta_notify, + .set_key = mt7601u_set_key, + .conf_tx = mt7601u_conf_tx, + .sw_scan_start = mt7601u_sw_scan, + .sw_scan_complete = mt7601u_sw_scan_complete, + .ampdu_action = mt76_ampdu_action, + .sta_rate_tbl_update = mt76_sta_rate_tbl_update, + .set_rts_threshold = mt7601u_set_rts_threshold, +}; diff --git a/drivers/net/wireless/mediatek/mt7601u/mcu.c b/drivers/net/wireless/mediatek/mt7601u/mcu.c new file mode 100644 index 0000000000000..fbb1986eda3cb --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/mcu.c @@ -0,0 +1,534 @@ +/* + * (c) Copyright 2002-2010, Ralink Technology, Inc. + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "dma.h" +#include "mcu.h" +#include "usb.h" +#include "trace.h" + +#define MCU_FW_URB_MAX_PAYLOAD 0x3800 +#define MCU_FW_URB_SIZE (MCU_FW_URB_MAX_PAYLOAD + 12) +#define MCU_RESP_URB_SIZE 1024 + +static inline int firmware_running(struct mt7601u_dev *dev) +{ + return mt7601u_rr(dev, MT_MCU_COM_REG0) == 1; +} + +static inline void skb_put_le32(struct sk_buff *skb, u32 val) +{ + put_unaligned_le32(val, skb_put(skb, 4)); +} + +static inline void mt7601u_dma_skb_wrap_cmd(struct sk_buff *skb, + u8 seq, enum mcu_cmd cmd) +{ + WARN_ON(mt7601u_dma_skb_wrap(skb, CPU_TX_PORT, DMA_COMMAND, + MT76_SET(MT_TXD_CMD_INFO_SEQ, seq) | + MT76_SET(MT_TXD_CMD_INFO_TYPE, cmd))); +} + +static inline void trace_mt_mcu_msg_send_cs(struct mt7601u_dev *dev, + struct sk_buff *skb, bool need_resp) +{ + u32 i, csum = 0; + + for (i = 0; i < skb->len / 4; i++) + csum ^= get_unaligned_le32(skb->data + i * 4); + + trace_mt_mcu_msg_send(dev, skb, csum, need_resp); +} + +static struct sk_buff * +mt7601u_mcu_msg_alloc(struct mt7601u_dev *dev, const void *data, int len) +{ + struct sk_buff *skb; + + WARN_ON(len % 4); /* if length is not divisible by 4 we need to pad */ + + skb = alloc_skb(len + MT_DMA_HDR_LEN + 4, GFP_KERNEL); + skb_reserve(skb, MT_DMA_HDR_LEN); + memcpy(skb_put(skb, len), data, len); + + return skb; +} + +static int mt7601u_mcu_wait_resp(struct mt7601u_dev *dev, u8 seq) +{ + struct urb *urb = dev->mcu.resp.urb; + u32 rxfce; + int urb_status, ret, i = 5; + + while (i--) { + if (!wait_for_completion_timeout(&dev->mcu.resp_cmpl, + msecs_to_jiffies(300))) { + dev_warn(dev->dev, "Warning: %s retrying\n", __func__); + continue; + } + + /* Make copies of important data before reusing the urb */ + rxfce = get_unaligned_le32(dev->mcu.resp.buf); + urb_status = urb->status * mt7601u_urb_has_error(urb); + + ret = mt7601u_usb_submit_buf(dev, USB_DIR_IN, MT_EP_IN_CMD_RESP, + &dev->mcu.resp, GFP_KERNEL, + mt7601u_complete_urb, + &dev->mcu.resp_cmpl); + if (ret) + return ret; + + if (urb_status) + dev_err(dev->dev, "Error: MCU resp urb failed:%d\n", + urb_status); + + if (MT76_GET(MT_RXD_CMD_INFO_CMD_SEQ, rxfce) == seq && + MT76_GET(MT_RXD_CMD_INFO_EVT_TYPE, rxfce) == CMD_DONE) + return 0; + + dev_err(dev->dev, "Error: MCU resp evt:%hhx seq:%hhx-%hhx!\n", + MT76_GET(MT_RXD_CMD_INFO_EVT_TYPE, rxfce), + seq, MT76_GET(MT_RXD_CMD_INFO_CMD_SEQ, rxfce)); + } + + dev_err(dev->dev, "Error: %s timed out\n", __func__); + return -ETIMEDOUT; +} + +static int +mt7601u_mcu_msg_send(struct mt7601u_dev *dev, struct sk_buff *skb, + enum mcu_cmd cmd, bool wait_resp) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + unsigned cmd_pipe = usb_sndbulkpipe(usb_dev, + dev->out_eps[MT_EP_OUT_INBAND_CMD]); + int sent, ret; + u8 seq = 0; + + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return 0; + + mutex_lock(&dev->mcu.mutex); + + if (wait_resp) + while (!seq) + seq = ++dev->mcu.msg_seq & 0xf; + + mt7601u_dma_skb_wrap_cmd(skb, seq, cmd); + + if (dev->mcu.resp_cmpl.done) + dev_err(dev->dev, "Error: MCU response pre-completed!\n"); + + trace_mt_mcu_msg_send_cs(dev, skb, wait_resp); + trace_mt_submit_urb_sync(dev, cmd_pipe, skb->len); + ret = usb_bulk_msg(usb_dev, cmd_pipe, skb->data, skb->len, &sent, 500); + if (ret) { + dev_err(dev->dev, "Error: send MCU cmd failed:%d\n", ret); + goto out; + } + if (sent != skb->len) + dev_err(dev->dev, "Error: %s sent != skb->len\n", __func__); + + if (wait_resp) + ret = mt7601u_mcu_wait_resp(dev, seq); +out: + mutex_unlock(&dev->mcu.mutex); + + consume_skb(skb); + + return ret; +} + +static int mt7601u_mcu_function_select(struct mt7601u_dev *dev, + enum mcu_function func, u32 val) +{ + struct sk_buff *skb; + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(func), + .value = cpu_to_le32(val), + }; + + skb = mt7601u_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt7601u_mcu_msg_send(dev, skb, CMD_FUN_SET_OP, func == 5); +} + +int mt7601u_mcu_tssi_read_kick(struct mt7601u_dev *dev, int use_hvga) +{ + int ret; + + if (!test_bit(MT7601U_STATE_MCU_RUNNING, &dev->state)) + return 0; + + ret = mt7601u_mcu_function_select(dev, ATOMIC_TSSI_SETTING, + use_hvga); + if (ret) { + dev_warn(dev->dev, "Warning: MCU TSSI read kick failed\n"); + return ret; + } + + dev->tssi_read_trig = true; + + return 0; +} + +int +mt7601u_mcu_calibrate(struct mt7601u_dev *dev, enum mcu_calibrate cal, u32 val) +{ + struct sk_buff *skb; + struct { + __le32 id; + __le32 value; + } __packed __aligned(4) msg = { + .id = cpu_to_le32(cal), + .value = cpu_to_le32(val), + }; + + skb = mt7601u_mcu_msg_alloc(dev, &msg, sizeof(msg)); + return mt7601u_mcu_msg_send(dev, skb, CMD_CALIBRATION_OP, true); +} + +int mt7601u_write_reg_pairs(struct mt7601u_dev *dev, u32 base, + const struct mt76_reg_pair *data, int n) +{ + const int max_vals_per_cmd = INBAND_PACKET_MAX_LEN / 8; + struct sk_buff *skb; + int cnt, i, ret; + + if (!n) + return 0; + + cnt = min(max_vals_per_cmd, n); + + skb = alloc_skb(cnt * 8 + MT_DMA_HDR_LEN + 4, GFP_KERNEL); + if (!skb) + return -ENOMEM; + skb_reserve(skb, MT_DMA_HDR_LEN); + + for (i = 0; i < cnt; i++) { + skb_put_le32(skb, base + data[i].reg); + skb_put_le32(skb, data[i].value); + } + + ret = mt7601u_mcu_msg_send(dev, skb, CMD_RANDOM_WRITE, cnt == n); + if (ret) + return ret; + + return mt7601u_write_reg_pairs(dev, base, data + cnt, n - cnt); +} + +int mt7601u_burst_write_regs(struct mt7601u_dev *dev, u32 offset, + const u32 *data, int n) +{ + const int max_regs_per_cmd = INBAND_PACKET_MAX_LEN / 4 - 1; + struct sk_buff *skb; + int cnt, i, ret; + + if (!n) + return 0; + + cnt = min(max_regs_per_cmd, n); + + skb = alloc_skb(cnt * 4 + MT_DMA_HDR_LEN + 4, GFP_KERNEL); + if (!skb) + return -ENOMEM; + skb_reserve(skb, MT_DMA_HDR_LEN); + + skb_put_le32(skb, MT_MCU_MEMMAP_WLAN + offset); + for (i = 0; i < cnt; i++) + skb_put_le32(skb, data[i]); + + ret = mt7601u_mcu_msg_send(dev, skb, CMD_BURST_WRITE, cnt == n); + if (ret) + return ret; + + return mt7601u_burst_write_regs(dev, offset + cnt * 4, + data + cnt, n - cnt); +} + +struct mt76_fw_header { + __le32 ilm_len; + __le32 dlm_len; + __le16 build_ver; + __le16 fw_ver; + u8 pad[4]; + char build_time[16]; +}; + +struct mt76_fw { + struct mt76_fw_header hdr; + u8 ivb[MT_MCU_IVB_SIZE]; + u8 ilm[]; +}; + +static int __mt7601u_dma_fw(struct mt7601u_dev *dev, + const struct mt7601u_dma_buf *dma_buf, + const void *data, u32 len, u32 dst_addr) +{ + DECLARE_COMPLETION_ONSTACK(cmpl); + struct mt7601u_dma_buf buf = *dma_buf; /* we need to fake length */ + __le32 reg; + u32 val; + int ret; + + reg = cpu_to_le32(MT76_SET(MT_TXD_INFO_TYPE, DMA_PACKET) | + MT76_SET(MT_TXD_INFO_D_PORT, CPU_TX_PORT) | + MT76_SET(MT_TXD_INFO_LEN, len)); + memcpy(buf.buf, ®, sizeof(reg)); + memcpy(buf.buf + sizeof(reg), data, len); + memset(buf.buf + sizeof(reg) + len, 0, 8); + + ret = mt7601u_vendor_single_wr(dev, MT_VEND_WRITE_FCE, + MT_FCE_DMA_ADDR, dst_addr); + if (ret) + return ret; + len = roundup(len, 4); + ret = mt7601u_vendor_single_wr(dev, MT_VEND_WRITE_FCE, + MT_FCE_DMA_LEN, len << 16); + if (ret) + return ret; + + buf.len = MT_DMA_HDR_LEN + len + 4; + ret = mt7601u_usb_submit_buf(dev, USB_DIR_OUT, MT_EP_OUT_INBAND_CMD, + &buf, GFP_KERNEL, + mt7601u_complete_urb, &cmpl); + if (ret) + return ret; + + if (!wait_for_completion_timeout(&cmpl, msecs_to_jiffies(1000))) { + dev_err(dev->dev, "Error: firmware upload timed out\n"); + usb_kill_urb(buf.urb); + return -ETIMEDOUT; + } + if (mt7601u_urb_has_error(buf.urb)) { + dev_err(dev->dev, "Error: firmware upload urb failed:%d\n", + buf.urb->status); + return buf.urb->status; + } + + val = mt7601u_rr(dev, MT_TX_CPU_FROM_FCE_CPU_DESC_IDX); + val++; + mt7601u_wr(dev, MT_TX_CPU_FROM_FCE_CPU_DESC_IDX, val); + + return 0; +} + +static int +mt7601u_dma_fw(struct mt7601u_dev *dev, struct mt7601u_dma_buf *dma_buf, + const void *data, int len, u32 dst_addr) +{ + int n, ret; + + if (len == 0) + return 0; + + n = min(MCU_FW_URB_MAX_PAYLOAD, len); + ret = __mt7601u_dma_fw(dev, dma_buf, data, n, dst_addr); + if (ret) + return ret; + + if (!mt76_poll_msec(dev, MT_MCU_COM_REG1, BIT(31), BIT(31), 500)) + return -ETIMEDOUT; + + return mt7601u_dma_fw(dev, dma_buf, data + n, len - n, dst_addr + n); +} + +static int +mt7601u_upload_firmware(struct mt7601u_dev *dev, const struct mt76_fw *fw) +{ + struct mt7601u_dma_buf dma_buf; + void *ivb; + u32 ilm_len, dlm_len; + int i, ret; + + ivb = kmemdup(fw->ivb, sizeof(fw->ivb), GFP_KERNEL); + if (!ivb || mt7601u_usb_alloc_buf(dev, MCU_FW_URB_SIZE, &dma_buf)) { + ret = -ENOMEM; + goto error; + } + + ilm_len = le32_to_cpu(fw->hdr.ilm_len) - sizeof(fw->ivb); + dev_dbg(dev->dev, "loading FW - ILM %u + IVB %zu\n", + ilm_len, sizeof(fw->ivb)); + ret = mt7601u_dma_fw(dev, &dma_buf, fw->ilm, ilm_len, sizeof(fw->ivb)); + if (ret) + goto error; + + dlm_len = le32_to_cpu(fw->hdr.dlm_len); + dev_dbg(dev->dev, "loading FW - DLM %u\n", dlm_len); + ret = mt7601u_dma_fw(dev, &dma_buf, fw->ilm + ilm_len, + dlm_len, MT_MCU_DLM_OFFSET); + if (ret) + goto error; + + ret = mt7601u_vendor_request(dev, MT_VEND_DEV_MODE, USB_DIR_OUT, + 0x12, 0, ivb, sizeof(fw->ivb)); + if (ret < 0) + goto error; + ret = 0; + + for (i = 100; i && !firmware_running(dev); i--) + msleep(10); + if (!i) { + ret = -ETIMEDOUT; + goto error; + } + + dev_dbg(dev->dev, "Firmware running!\n"); +error: + kfree(ivb); + mt7601u_usb_free_buf(dev, &dma_buf); + + return ret; +} + +static int mt7601u_load_firmware(struct mt7601u_dev *dev) +{ + const struct firmware *fw; + const struct mt76_fw_header *hdr; + int len, ret; + u32 val; + + mt7601u_wr(dev, MT_USB_DMA_CFG, (MT_USB_DMA_CFG_RX_BULK_EN | + MT_USB_DMA_CFG_TX_BULK_EN)); + + if (firmware_running(dev)) + return 0; + + ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev); + if (ret) + return ret; + + if (!fw || !fw->data || fw->size < sizeof(*hdr)) + goto err_inv_fw; + + hdr = (const struct mt76_fw_header *) fw->data; + + if (le32_to_cpu(hdr->ilm_len) <= MT_MCU_IVB_SIZE) + goto err_inv_fw; + + len = sizeof(*hdr); + len += le32_to_cpu(hdr->ilm_len); + len += le32_to_cpu(hdr->dlm_len); + + if (fw->size != len) + goto err_inv_fw; + + val = le16_to_cpu(hdr->fw_ver); + dev_info(dev->dev, + "Firmware Version: %d.%d.%02d Build: %x Build time: %.16s\n", + (val >> 12) & 0xf, (val >> 8) & 0xf, val & 0xf, + le16_to_cpu(hdr->build_ver), hdr->build_time); + + len = le32_to_cpu(hdr->ilm_len); + + mt7601u_wr(dev, 0x94c, 0); + mt7601u_wr(dev, MT_FCE_PSE_CTRL, 0); + + mt7601u_vendor_reset(dev); + msleep(5); + + mt7601u_wr(dev, 0xa44, 0); + mt7601u_wr(dev, 0x230, 0x84210); + mt7601u_wr(dev, 0x400, 0x80c00); + mt7601u_wr(dev, 0x800, 1); + + mt7601u_rmw(dev, MT_PBF_CFG, 0, (MT_PBF_CFG_TX0Q_EN | + MT_PBF_CFG_TX1Q_EN | + MT_PBF_CFG_TX2Q_EN | + MT_PBF_CFG_TX3Q_EN)); + + mt7601u_wr(dev, MT_FCE_PSE_CTRL, 1); + + mt7601u_wr(dev, MT_USB_DMA_CFG, (MT_USB_DMA_CFG_RX_BULK_EN | + MT_USB_DMA_CFG_TX_BULK_EN)); + val = mt76_set(dev, MT_USB_DMA_CFG, MT_USB_DMA_CFG_TX_CLR); + val &= ~MT_USB_DMA_CFG_TX_CLR; + mt7601u_wr(dev, MT_USB_DMA_CFG, val); + + /* FCE tx_fs_base_ptr */ + mt7601u_wr(dev, MT_TX_CPU_FROM_FCE_BASE_PTR, 0x400230); + /* FCE tx_fs_max_cnt */ + mt7601u_wr(dev, MT_TX_CPU_FROM_FCE_MAX_COUNT, 1); + /* FCE pdma enable */ + mt7601u_wr(dev, MT_FCE_PDMA_GLOBAL_CONF, 0x44); + /* FCE skip_fs_en */ + mt7601u_wr(dev, MT_FCE_SKIP_FS, 3); + + ret = mt7601u_upload_firmware(dev, (const struct mt76_fw *)fw->data); + + release_firmware(fw); + + return ret; + +err_inv_fw: + dev_err(dev->dev, "Invalid firmware image\n"); + release_firmware(fw); + return -ENOENT; +} + +int mt7601u_mcu_init(struct mt7601u_dev *dev) +{ + int ret; + + mutex_init(&dev->mcu.mutex); + + ret = mt7601u_load_firmware(dev); + if (ret) + return ret; + + set_bit(MT7601U_STATE_MCU_RUNNING, &dev->state); + + return 0; +} + +int mt7601u_mcu_cmd_init(struct mt7601u_dev *dev) +{ + int ret; + + ret = mt7601u_mcu_function_select(dev, Q_SELECT, 1); + if (ret) + return ret; + + init_completion(&dev->mcu.resp_cmpl); + if (mt7601u_usb_alloc_buf(dev, MCU_RESP_URB_SIZE, &dev->mcu.resp)) { + mt7601u_usb_free_buf(dev, &dev->mcu.resp); + return -ENOMEM; + } + + ret = mt7601u_usb_submit_buf(dev, USB_DIR_IN, MT_EP_IN_CMD_RESP, + &dev->mcu.resp, GFP_KERNEL, + mt7601u_complete_urb, &dev->mcu.resp_cmpl); + if (ret) { + mt7601u_usb_free_buf(dev, &dev->mcu.resp); + return ret; + } + + return 0; +} + +void mt7601u_mcu_cmd_deinit(struct mt7601u_dev *dev) +{ + usb_kill_urb(dev->mcu.resp.urb); + mt7601u_usb_free_buf(dev, &dev->mcu.resp); +} diff --git a/drivers/net/wireless/mediatek/mt7601u/mcu.h b/drivers/net/wireless/mediatek/mt7601u/mcu.h new file mode 100644 index 0000000000000..4a66d1092a186 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/mcu.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_MCU_H +#define __MT7601U_MCU_H + +struct mt7601u_dev; + +/* Register definitions */ +#define MT_MCU_RESET_CTL 0x070C +#define MT_MCU_INT_LEVEL 0x0718 +#define MT_MCU_COM_REG0 0x0730 +#define MT_MCU_COM_REG1 0x0734 +#define MT_MCU_COM_REG2 0x0738 +#define MT_MCU_COM_REG3 0x073C + +#define MT_MCU_IVB_SIZE 0x40 +#define MT_MCU_DLM_OFFSET 0x80000 + +#define MT_MCU_MEMMAP_WLAN 0x00410000 +#define MT_MCU_MEMMAP_BBP 0x40000000 +#define MT_MCU_MEMMAP_RF 0x80000000 + +#define INBAND_PACKET_MAX_LEN 192 + +enum mcu_cmd { + CMD_FUN_SET_OP = 1, + CMD_LOAD_CR = 2, + CMD_INIT_GAIN_OP = 3, + CMD_DYNC_VGA_OP = 6, + CMD_TDLS_CH_SW = 7, + CMD_BURST_WRITE = 8, + CMD_READ_MODIFY_WRITE = 9, + CMD_RANDOM_READ = 10, + CMD_BURST_READ = 11, + CMD_RANDOM_WRITE = 12, + CMD_LED_MODE_OP = 16, + CMD_POWER_SAVING_OP = 20, + CMD_WOW_CONFIG = 21, + CMD_WOW_QUERY = 22, + CMD_WOW_FEATURE = 24, + CMD_CARRIER_DETECT_OP = 28, + CMD_RADOR_DETECT_OP = 29, + CMD_SWITCH_CHANNEL_OP = 30, + CMD_CALIBRATION_OP = 31, + CMD_BEACON_OP = 32, + CMD_ANTENNA_OP = 33, +}; + +enum mcu_function { + Q_SELECT = 1, + ATOMIC_TSSI_SETTING = 5, +}; + +enum mcu_power_mode { + RADIO_OFF = 0x30, + RADIO_ON = 0x31, + RADIO_OFF_AUTO_WAKEUP = 0x32, + RADIO_OFF_ADVANCE = 0x33, + RADIO_ON_ADVANCE = 0x34, +}; + +enum mcu_calibrate { + MCU_CAL_R = 1, + MCU_CAL_DCOC, + MCU_CAL_LC, + MCU_CAL_LOFT, + MCU_CAL_TXIQ, + MCU_CAL_BW, + MCU_CAL_DPD, + MCU_CAL_RXIQ, + MCU_CAL_TXDCOC, +}; + +int mt7601u_mcu_init(struct mt7601u_dev *dev); +int mt7601u_mcu_cmd_init(struct mt7601u_dev *dev); +void mt7601u_mcu_cmd_deinit(struct mt7601u_dev *dev); + +int +mt7601u_mcu_calibrate(struct mt7601u_dev *dev, enum mcu_calibrate cal, u32 val); +int mt7601u_mcu_tssi_read_kick(struct mt7601u_dev *dev, int use_hvga); + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/mt7601u.h b/drivers/net/wireless/mediatek/mt7601u/mt7601u.h new file mode 100644 index 0000000000000..9102be6b95cb7 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/mt7601u.h @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 MT7601U_H +#define MT7601U_H + +#include +#include +#include +#include +#include +#include +#include + +#include "regs.h" +#include "util.h" + +#define MT_CALIBRATE_INTERVAL (4 * HZ) + +#define MT_FREQ_CAL_INIT_DELAY (30 * HZ) +#define MT_FREQ_CAL_CHECK_INTERVAL (10 * HZ) +#define MT_FREQ_CAL_ADJ_INTERVAL (HZ / 2) + +#define MT_BBP_REG_VERSION 0x00 + +#define MT_USB_AGGR_SIZE_LIMIT 28 /* * 1024B */ +#define MT_USB_AGGR_TIMEOUT 0x80 /* * 33ns */ +#define MT_RX_ORDER 3 +#define MT_RX_URB_SIZE (PAGE_SIZE << MT_RX_ORDER) + +struct mt7601u_dma_buf { + struct urb *urb; + void *buf; + dma_addr_t dma; + size_t len; +}; + +struct mt7601u_mcu { + struct mutex mutex; + + u8 msg_seq; + + struct mt7601u_dma_buf resp; + struct completion resp_cmpl; +}; + +struct mt7601u_freq_cal { + struct delayed_work work; + u8 freq; + bool enabled; + bool adjusting; +}; + +struct mac_stats { + u64 rx_stat[6]; + u64 tx_stat[6]; + u64 aggr_stat[2]; + u64 aggr_n[32]; + u64 zero_len_del[2]; +}; + +#define N_RX_ENTRIES 16 +struct mt7601u_rx_queue { + struct mt7601u_dev *dev; + + struct mt7601u_dma_buf_rx { + struct urb *urb; + struct page *p; + } e[N_RX_ENTRIES]; + + unsigned int start; + unsigned int end; + unsigned int entries; + unsigned int pending; +}; + +#define N_TX_ENTRIES 64 + +struct mt7601u_tx_queue { + struct mt7601u_dev *dev; + + struct mt7601u_dma_buf_tx { + struct urb *urb; + struct sk_buff *skb; + } e[N_TX_ENTRIES]; + + unsigned int start; + unsigned int end; + unsigned int entries; + unsigned int used; + unsigned int fifo_seq; +}; + +/* WCID allocation: + * 0: mcast wcid + * 1: bssid wcid + * 1...: STAs + * ...7e: group wcids + * 7f: reserved + */ +#define N_WCIDS 128 +#define GROUP_WCID(idx) (N_WCIDS - 2 - idx) + +struct mt7601u_eeprom_params; + +#define MT_EE_TEMPERATURE_SLOPE 39 +#define MT_FREQ_OFFSET_INVALID -128 + +enum mt_temp_mode { + MT_TEMP_MODE_NORMAL, + MT_TEMP_MODE_HIGH, + MT_TEMP_MODE_LOW, +}; + +enum mt_bw { + MT_BW_20, + MT_BW_40, +}; + +enum { + MT7601U_STATE_INITIALIZED, + MT7601U_STATE_REMOVED, + MT7601U_STATE_WLAN_RUNNING, + MT7601U_STATE_MCU_RUNNING, + MT7601U_STATE_SCANNING, + MT7601U_STATE_READING_STATS, + MT7601U_STATE_MORE_STATS, +}; + +/** + * struct mt7601u_dev - adapter structure + * @lock: protects @wcid->tx_rate. + * @tx_lock: protects @tx_q and changes of MT7601U_STATE_*_STATS + flags in @state. + * @rx_lock: protects @rx_q. + * @con_mon_lock: protects @ap_bssid, @bcn_*, @avg_rssi. + * @mutex: ensures exclusive access from mac80211 callbacks. + * @vendor_req_mutex: ensures atomicity of vendor requests. + * @reg_atomic_mutex: ensures atomicity of indirect register accesses + * (accesses to RF and BBP). + * @hw_atomic_mutex: ensures exclusive access to HW during critical + * operations (power management, channel switch). + */ +struct mt7601u_dev { + struct ieee80211_hw *hw; + struct device *dev; + + unsigned long state; + + struct mutex mutex; + + unsigned long wcid_mask[N_WCIDS / BITS_PER_LONG]; + + struct cfg80211_chan_def chandef; + struct ieee80211_supported_band *sband_2g; + + struct mt7601u_mcu mcu; + + struct delayed_work cal_work; + struct delayed_work mac_work; + + struct workqueue_struct *stat_wq; + struct delayed_work stat_work; + + struct mt76_wcid *mon_wcid; + struct mt76_wcid __rcu *wcid[N_WCIDS]; + + spinlock_t lock; + + const u16 *beacon_offsets; + + u8 macaddr[ETH_ALEN]; + struct mt7601u_eeprom_params *ee; + + struct mutex vendor_req_mutex; + struct mutex reg_atomic_mutex; + struct mutex hw_atomic_mutex; + + u32 rxfilter; + u32 debugfs_reg; + + u8 out_eps[8]; + u8 in_eps[8]; + u16 out_max_packet; + u16 in_max_packet; + + /* TX */ + spinlock_t tx_lock; + struct mt7601u_tx_queue *tx_q; + + atomic_t avg_ampdu_len; + + /* RX */ + spinlock_t rx_lock; + struct tasklet_struct rx_tasklet; + struct mt7601u_rx_queue rx_q; + + /* Connection monitoring things */ + spinlock_t con_mon_lock; + u8 ap_bssid[ETH_ALEN]; + + s8 bcn_freq_off; + u8 bcn_phy_mode; + + int avg_rssi; /* starts at 0 and converges */ + + u8 agc_save; + + struct mt7601u_freq_cal freq_cal; + + bool tssi_read_trig; + + s8 tssi_init; + s8 tssi_init_hvga; + s16 tssi_init_hvga_offset_db; + + int prev_pwr_diff; + + enum mt_temp_mode temp_mode; + int curr_temp; + int dpd_temp; + s8 raw_temp; + bool pll_lock_protect; + + u8 bw; + bool chan_ext_below; + + /* PA mode */ + u32 rf_pa_mode[2]; + + struct mac_stats stats; +}; + +struct mt7601u_tssi_params { + char tssi0; + int trgt_power; +}; + +struct mt76_wcid { + u8 idx; + u8 hw_key_idx; + + u16 tx_rate; + bool tx_rate_set; + u8 tx_rate_nss; +}; + +struct mt76_vif { + u8 idx; + + struct mt76_wcid group_wcid; +}; + +struct mt76_sta { + struct mt76_wcid wcid; + u16 agg_ssn[IEEE80211_NUM_TIDS]; +}; + +struct mt76_reg_pair { + u32 reg; + u32 value; +}; + +struct mt7601u_rxwi; + +extern const struct ieee80211_ops mt7601u_ops; + +void mt7601u_init_debugfs(struct mt7601u_dev *dev); + +u32 mt7601u_rr(struct mt7601u_dev *dev, u32 offset); +void mt7601u_wr(struct mt7601u_dev *dev, u32 offset, u32 val); +u32 mt7601u_rmw(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val); +u32 mt7601u_rmc(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val); +void mt7601u_wr_copy(struct mt7601u_dev *dev, u32 offset, + const void *data, int len); + +int mt7601u_wait_asic_ready(struct mt7601u_dev *dev); +bool mt76_poll(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); +bool mt76_poll_msec(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val, + int timeout); + +/* Compatibility with mt76 */ +#define mt76_rmw_field(_dev, _reg, _field, _val) \ + mt76_rmw(_dev, _reg, _field, MT76_SET(_field, _val)) + +static inline u32 mt76_rr(struct mt7601u_dev *dev, u32 offset) +{ + return mt7601u_rr(dev, offset); +} + +static inline void mt76_wr(struct mt7601u_dev *dev, u32 offset, u32 val) +{ + return mt7601u_wr(dev, offset, val); +} + +static inline u32 +mt76_rmw(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val) +{ + return mt7601u_rmw(dev, offset, mask, val); +} + +static inline u32 mt76_set(struct mt7601u_dev *dev, u32 offset, u32 val) +{ + return mt76_rmw(dev, offset, 0, val); +} + +static inline u32 mt76_clear(struct mt7601u_dev *dev, u32 offset, u32 val) +{ + return mt76_rmw(dev, offset, val, 0); +} + +int mt7601u_write_reg_pairs(struct mt7601u_dev *dev, u32 base, + const struct mt76_reg_pair *data, int len); +int mt7601u_burst_write_regs(struct mt7601u_dev *dev, u32 offset, + const u32 *data, int n); +void mt7601u_addr_wr(struct mt7601u_dev *dev, const u32 offset, const u8 *addr); + +/* Init */ +struct mt7601u_dev *mt7601u_alloc_device(struct device *dev); +int mt7601u_init_hardware(struct mt7601u_dev *dev); +int mt7601u_register_device(struct mt7601u_dev *dev); +void mt7601u_cleanup(struct mt7601u_dev *dev); + +int mt7601u_mac_start(struct mt7601u_dev *dev); +void mt7601u_mac_stop(struct mt7601u_dev *dev); + +/* PHY */ +int mt7601u_phy_init(struct mt7601u_dev *dev); +int mt7601u_wait_bbp_ready(struct mt7601u_dev *dev); +void mt7601u_set_rx_path(struct mt7601u_dev *dev, u8 path); +void mt7601u_set_tx_dac(struct mt7601u_dev *dev, u8 path); +int mt7601u_bbp_set_bw(struct mt7601u_dev *dev, int bw); +void mt7601u_agc_save(struct mt7601u_dev *dev); +void mt7601u_agc_restore(struct mt7601u_dev *dev); +int mt7601u_phy_set_channel(struct mt7601u_dev *dev, + struct cfg80211_chan_def *chandef); +void mt7601u_phy_recalibrate_after_assoc(struct mt7601u_dev *dev); +int mt7601u_phy_get_rssi(struct mt7601u_dev *dev, + struct mt7601u_rxwi *rxwi, u16 rate); +void mt7601u_phy_con_cal_onoff(struct mt7601u_dev *dev, + struct ieee80211_bss_conf *info); + +/* MAC */ +void mt7601u_mac_work(struct work_struct *work); +void mt7601u_mac_set_protection(struct mt7601u_dev *dev, bool legacy_prot, + int ht_mode); +void mt7601u_mac_set_short_preamble(struct mt7601u_dev *dev, bool short_preamb); +void mt7601u_mac_config_tsf(struct mt7601u_dev *dev, bool enable, int interval); +void +mt7601u_mac_wcid_setup(struct mt7601u_dev *dev, u8 idx, u8 vif_idx, u8 *mac); +void mt7601u_mac_set_ampdu_factor(struct mt7601u_dev *dev); + +/* TX */ +void mt7601u_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb); +int mt7601u_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params); +void mt7601u_tx_status(struct mt7601u_dev *dev, struct sk_buff *skb); +void mt7601u_tx_stat(struct work_struct *work); + +/* util */ +void mt76_remove_hdr_pad(struct sk_buff *skb); +int mt76_insert_hdr_pad(struct sk_buff *skb); + +u32 mt7601u_bbp_set_ctrlch(struct mt7601u_dev *dev, bool below); + +static inline u32 mt7601u_mac_set_ctrlch(struct mt7601u_dev *dev, bool below) +{ + return mt7601u_rmc(dev, MT_TX_BAND_CFG, 1, below); +} + +int mt7601u_dma_init(struct mt7601u_dev *dev); +void mt7601u_dma_cleanup(struct mt7601u_dev *dev); + +int mt7601u_dma_enqueue_tx(struct mt7601u_dev *dev, struct sk_buff *skb, + struct mt76_wcid *wcid, int hw_q); + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/phy.c b/drivers/net/wireless/mediatek/mt7601u/phy.c new file mode 100644 index 0000000000000..1908af6add87a --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/phy.c @@ -0,0 +1,1251 @@ +/* + * (c) Copyright 2002-2010, Ralink Technology, Inc. + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "mcu.h" +#include "eeprom.h" +#include "trace.h" +#include "initvals_phy.h" + +#include + +static void mt7601u_agc_reset(struct mt7601u_dev *dev); + +static int +mt7601u_rf_wr(struct mt7601u_dev *dev, u8 bank, u8 offset, u8 value) +{ + int ret = 0; + + if (WARN_ON(!test_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state)) || + WARN_ON(offset > 63)) + return -EINVAL; + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return 0; + + mutex_lock(&dev->reg_atomic_mutex); + + if (!mt76_poll(dev, MT_RF_CSR_CFG, MT_RF_CSR_CFG_KICK, 0, 100)) { + ret = -ETIMEDOUT; + goto out; + } + + mt7601u_wr(dev, MT_RF_CSR_CFG, MT76_SET(MT_RF_CSR_CFG_DATA, value) | + MT76_SET(MT_RF_CSR_CFG_REG_BANK, bank) | + MT76_SET(MT_RF_CSR_CFG_REG_ID, offset) | + MT_RF_CSR_CFG_WR | + MT_RF_CSR_CFG_KICK); + trace_rf_write(dev, bank, offset, value); +out: + mutex_unlock(&dev->reg_atomic_mutex); + + if (ret < 0) + dev_err(dev->dev, "Error: RF write %02hhx:%02hhx failed:%d!!\n", + bank, offset, ret); + + return ret; +} + +static int +mt7601u_rf_rr(struct mt7601u_dev *dev, u8 bank, u8 offset) +{ + int ret = -ETIMEDOUT; + u32 val; + + if (WARN_ON(!test_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state)) || + WARN_ON(offset > 63)) + return -EINVAL; + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return 0xff; + + mutex_lock(&dev->reg_atomic_mutex); + + if (!mt76_poll(dev, MT_RF_CSR_CFG, MT_RF_CSR_CFG_KICK, 0, 100)) + goto out; + + mt7601u_wr(dev, MT_RF_CSR_CFG, MT76_SET(MT_RF_CSR_CFG_REG_BANK, bank) | + MT76_SET(MT_RF_CSR_CFG_REG_ID, offset) | + MT_RF_CSR_CFG_KICK); + + if (!mt76_poll(dev, MT_RF_CSR_CFG, MT_RF_CSR_CFG_KICK, 0, 100)) + goto out; + + val = mt7601u_rr(dev, MT_RF_CSR_CFG); + if (MT76_GET(MT_RF_CSR_CFG_REG_ID, val) == offset && + MT76_GET(MT_RF_CSR_CFG_REG_BANK, val) == bank) { + ret = MT76_GET(MT_RF_CSR_CFG_DATA, val); + trace_rf_read(dev, bank, offset, ret); + } +out: + mutex_unlock(&dev->reg_atomic_mutex); + + if (ret < 0) + dev_err(dev->dev, "Error: RF read %02hhx:%02hhx failed:%d!!\n", + bank, offset, ret); + + return ret; +} + +static int +mt7601u_rf_rmw(struct mt7601u_dev *dev, u8 bank, u8 offset, u8 mask, u8 val) +{ + int ret; + + ret = mt7601u_rf_rr(dev, bank, offset); + if (ret < 0) + return ret; + val |= ret & ~mask; + ret = mt7601u_rf_wr(dev, bank, offset, val); + if (ret) + return ret; + + return val; +} + +static int +mt7601u_rf_set(struct mt7601u_dev *dev, u8 bank, u8 offset, u8 val) +{ + return mt7601u_rf_rmw(dev, bank, offset, 0, val); +} + +static int +mt7601u_rf_clear(struct mt7601u_dev *dev, u8 bank, u8 offset, u8 mask) +{ + return mt7601u_rf_rmw(dev, bank, offset, mask, 0); +} + +static void mt7601u_bbp_wr(struct mt7601u_dev *dev, u8 offset, u8 val) +{ + if (WARN_ON(!test_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state)) || + test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return; + + mutex_lock(&dev->reg_atomic_mutex); + + if (!mt76_poll(dev, MT_BBP_CSR_CFG, MT_BBP_CSR_CFG_BUSY, 0, 1000)) { + dev_err(dev->dev, "Error: BBP write %02hhx failed!!\n", offset); + goto out; + } + + mt7601u_wr(dev, MT_BBP_CSR_CFG, + MT76_SET(MT_BBP_CSR_CFG_VAL, val) | + MT76_SET(MT_BBP_CSR_CFG_REG_NUM, offset) | + MT_BBP_CSR_CFG_RW_MODE | MT_BBP_CSR_CFG_BUSY); + trace_bbp_write(dev, offset, val); +out: + mutex_unlock(&dev->reg_atomic_mutex); +} + +static int mt7601u_bbp_rr(struct mt7601u_dev *dev, u8 offset) +{ + u32 val; + int ret = -ETIMEDOUT; + + if (WARN_ON(!test_bit(MT7601U_STATE_WLAN_RUNNING, &dev->state))) + return -EINVAL; + if (test_bit(MT7601U_STATE_REMOVED, &dev->state)) + return 0xff; + + mutex_lock(&dev->reg_atomic_mutex); + + if (!mt76_poll(dev, MT_BBP_CSR_CFG, MT_BBP_CSR_CFG_BUSY, 0, 1000)) + goto out; + + mt7601u_wr(dev, MT_BBP_CSR_CFG, + MT76_SET(MT_BBP_CSR_CFG_REG_NUM, offset) | + MT_BBP_CSR_CFG_RW_MODE | MT_BBP_CSR_CFG_BUSY | + MT_BBP_CSR_CFG_READ); + + if (!mt76_poll(dev, MT_BBP_CSR_CFG, MT_BBP_CSR_CFG_BUSY, 0, 1000)) + goto out; + + val = mt7601u_rr(dev, MT_BBP_CSR_CFG); + if (MT76_GET(MT_BBP_CSR_CFG_REG_NUM, val) == offset) { + ret = MT76_GET(MT_BBP_CSR_CFG_VAL, val); + trace_bbp_read(dev, offset, ret); + } +out: + mutex_unlock(&dev->reg_atomic_mutex); + + if (ret < 0) + dev_err(dev->dev, "Error: BBP read %02hhx failed:%d!!\n", + offset, ret); + + return ret; +} + +static int mt7601u_bbp_rmw(struct mt7601u_dev *dev, u8 offset, u8 mask, u8 val) +{ + int ret; + + ret = mt7601u_bbp_rr(dev, offset); + if (ret < 0) + return ret; + val |= ret & ~mask; + mt7601u_bbp_wr(dev, offset, val); + + return val; +} + +static u8 mt7601u_bbp_rmc(struct mt7601u_dev *dev, u8 offset, u8 mask, u8 val) +{ + int ret; + + ret = mt7601u_bbp_rr(dev, offset); + if (ret < 0) + return ret; + val |= ret & ~mask; + if (ret != val) + mt7601u_bbp_wr(dev, offset, val); + + return val; +} + +int mt7601u_wait_bbp_ready(struct mt7601u_dev *dev) +{ + int i = 20; + u8 val; + + do { + val = mt7601u_bbp_rr(dev, MT_BBP_REG_VERSION); + if (val && ~val) + break; + } while (--i); + + if (!i) { + dev_err(dev->dev, "Error: BBP is not ready\n"); + return -EIO; + } + + return 0; +} + +u32 mt7601u_bbp_set_ctrlch(struct mt7601u_dev *dev, bool below) +{ + return mt7601u_bbp_rmc(dev, 3, 0x20, below ? 0x20 : 0); +} + +int mt7601u_phy_get_rssi(struct mt7601u_dev *dev, + struct mt7601u_rxwi *rxwi, u16 rate) +{ + static const s8 lna[2][2][3] = { + /* main LNA */ { + /* bw20 */ { -2, 15, 33 }, + /* bw40 */ { 0, 16, 34 } + }, + /* aux LNA */ { + /* bw20 */ { -2, 15, 33 }, + /* bw40 */ { -2, 16, 34 } + } + }; + int bw = MT76_GET(MT_RXWI_RATE_BW, rate); + int aux_lna = MT76_GET(MT_RXWI_ANT_AUX_LNA, rxwi->ant); + int lna_id = MT76_GET(MT_RXWI_GAIN_RSSI_LNA_ID, rxwi->gain); + int val; + + if (lna_id) /* LNA id can be 0, 2, 3. */ + lna_id--; + + val = 8; + val -= lna[aux_lna][bw][lna_id]; + val -= MT76_GET(MT_RXWI_GAIN_RSSI_VAL, rxwi->gain); + val -= dev->ee->lna_gain; + val -= dev->ee->rssi_offset[0]; + + return val; +} + +static void mt7601u_vco_cal(struct mt7601u_dev *dev) +{ + mt7601u_rf_wr(dev, 0, 4, 0x0a); + mt7601u_rf_wr(dev, 0, 5, 0x20); + mt7601u_rf_set(dev, 0, 4, BIT(7)); + msleep(2); +} + +static int mt7601u_set_bw_filter(struct mt7601u_dev *dev, bool cal) +{ + u32 filter = 0; + int ret; + + if (!cal) + filter |= 0x10000; + if (dev->bw != MT_BW_20) + filter |= 0x00100; + + /* TX */ + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_BW, filter | 1); + if (ret) + return ret; + /* RX */ + return mt7601u_mcu_calibrate(dev, MCU_CAL_BW, filter); +} + +static int mt7601u_load_bbp_temp_table_bw(struct mt7601u_dev *dev) +{ + const struct reg_table *t; + + if (WARN_ON(dev->temp_mode > MT_TEMP_MODE_LOW)) + return -EINVAL; + + t = &bbp_mode_table[dev->temp_mode][dev->bw]; + + return mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, t->regs, t->n); +} + +static int mt7601u_bbp_temp(struct mt7601u_dev *dev, int mode, const char *name) +{ + const struct reg_table *t; + int ret; + + if (dev->temp_mode == mode) + return 0; + + dev->temp_mode = mode; + trace_temp_mode(dev, mode); + + t = bbp_mode_table[dev->temp_mode]; + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, + t[2].regs, t[2].n); + if (ret) + return ret; + + return mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, + t[dev->bw].regs, t[dev->bw].n); +} + +static void mt7601u_apply_ch14_fixup(struct mt7601u_dev *dev, int hw_chan) +{ + struct mt7601u_rate_power *t = &dev->ee->power_rate_table; + + if (hw_chan != 14 || dev->bw != MT_BW_20) { + mt7601u_bbp_rmw(dev, 4, 0x20, 0); + mt7601u_bbp_wr(dev, 178, 0xff); + + t->cck[0].bw20 = dev->ee->real_cck_bw20[0]; + t->cck[1].bw20 = dev->ee->real_cck_bw20[1]; + } else { /* Apply CH14 OBW fixup */ + mt7601u_bbp_wr(dev, 4, 0x60); + mt7601u_bbp_wr(dev, 178, 0); + + /* Note: vendor code is buggy here for negative values */ + t->cck[0].bw20 = dev->ee->real_cck_bw20[0] - 2; + t->cck[1].bw20 = dev->ee->real_cck_bw20[1] - 2; + } +} + +static int __mt7601u_phy_set_channel(struct mt7601u_dev *dev, + struct cfg80211_chan_def *chandef) +{ +#define FREQ_PLAN_REGS 4 + static const u8 freq_plan[14][FREQ_PLAN_REGS] = { + { 0x99, 0x99, 0x09, 0x50 }, + { 0x46, 0x44, 0x0a, 0x50 }, + { 0xec, 0xee, 0x0a, 0x50 }, + { 0x99, 0x99, 0x0b, 0x50 }, + { 0x46, 0x44, 0x08, 0x51 }, + { 0xec, 0xee, 0x08, 0x51 }, + { 0x99, 0x99, 0x09, 0x51 }, + { 0x46, 0x44, 0x0a, 0x51 }, + { 0xec, 0xee, 0x0a, 0x51 }, + { 0x99, 0x99, 0x0b, 0x51 }, + { 0x46, 0x44, 0x08, 0x52 }, + { 0xec, 0xee, 0x08, 0x52 }, + { 0x99, 0x99, 0x09, 0x52 }, + { 0x33, 0x33, 0x0b, 0x52 }, + }; + struct mt76_reg_pair channel_freq_plan[FREQ_PLAN_REGS] = { + { 17, 0 }, { 18, 0 }, { 19, 0 }, { 20, 0 }, + }; + struct mt76_reg_pair bbp_settings[3] = { + { 62, 0x37 - dev->ee->lna_gain }, + { 63, 0x37 - dev->ee->lna_gain }, + { 64, 0x37 - dev->ee->lna_gain }, + }; + + struct ieee80211_channel *chan = chandef->chan; + enum nl80211_channel_type chan_type = + cfg80211_get_chandef_type(chandef); + struct mt7601u_rate_power *t = &dev->ee->power_rate_table; + int chan_idx; + bool chan_ext_below; + u8 bw; + int i, ret; + + bw = MT_BW_20; + chan_ext_below = (chan_type == NL80211_CHAN_HT40MINUS); + chan_idx = chan->hw_value - 1; + + if (chandef->width == NL80211_CHAN_WIDTH_40) { + bw = MT_BW_40; + + if (chan_idx > 1 && chan_type == NL80211_CHAN_HT40MINUS) + chan_idx -= 2; + else if (chan_idx < 12 && chan_type == NL80211_CHAN_HT40PLUS) + chan_idx += 2; + else + dev_err(dev->dev, "Error: invalid 40MHz channel!!\n"); + } + + if (bw != dev->bw || chan_ext_below != dev->chan_ext_below) { + dev_dbg(dev->dev, "Info: switching HT mode bw:%d below:%d\n", + bw, chan_ext_below); + + mt7601u_bbp_set_bw(dev, bw); + + mt7601u_bbp_set_ctrlch(dev, chan_ext_below); + mt7601u_mac_set_ctrlch(dev, chan_ext_below); + dev->chan_ext_below = chan_ext_below; + } + + for (i = 0; i < FREQ_PLAN_REGS; i++) + channel_freq_plan[i].value = freq_plan[chan_idx][i]; + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_RF, + channel_freq_plan, FREQ_PLAN_REGS); + if (ret) + return ret; + + mt7601u_rmw(dev, MT_TX_ALC_CFG_0, 0x3f3f, + dev->ee->chan_pwr[chan_idx] & 0x3f); + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, + bbp_settings, ARRAY_SIZE(bbp_settings)); + if (ret) + return ret; + + mt7601u_vco_cal(dev); + mt7601u_bbp_set_bw(dev, bw); + ret = mt7601u_set_bw_filter(dev, false); + if (ret) + return ret; + + mt7601u_apply_ch14_fixup(dev, chan->hw_value); + mt7601u_wr(dev, MT_TX_PWR_CFG_0, int_to_s6(t->ofdm[1].bw20) << 24 | + int_to_s6(t->ofdm[0].bw20) << 16 | + int_to_s6(t->cck[1].bw20) << 8 | + int_to_s6(t->cck[0].bw20)); + + if (test_bit(MT7601U_STATE_SCANNING, &dev->state)) + mt7601u_agc_reset(dev); + + dev->chandef = *chandef; + + return 0; +} + +int mt7601u_phy_set_channel(struct mt7601u_dev *dev, + struct cfg80211_chan_def *chandef) +{ + int ret; + + cancel_delayed_work_sync(&dev->cal_work); + cancel_delayed_work_sync(&dev->freq_cal.work); + + mutex_lock(&dev->hw_atomic_mutex); + ret = __mt7601u_phy_set_channel(dev, chandef); + mutex_unlock(&dev->hw_atomic_mutex); + if (ret) + return ret; + + if (test_bit(MT7601U_STATE_SCANNING, &dev->state)) + return 0; + + ieee80211_queue_delayed_work(dev->hw, &dev->cal_work, + MT_CALIBRATE_INTERVAL); + if (dev->freq_cal.enabled) + ieee80211_queue_delayed_work(dev->hw, &dev->freq_cal.work, + MT_FREQ_CAL_INIT_DELAY); + return 0; +} + +#define BBP_R47_FLAG GENMASK(2, 0) +#define BBP_R47_F_TSSI 0 +#define BBP_R47_F_PKT_T 1 +#define BBP_R47_F_TX_RATE 2 +#define BBP_R47_F_TEMP 4 +/** + * mt7601u_bbp_r47_get - read value through BBP R47/R49 pair + * @dev: pointer to adapter structure + * @reg: value of BBP R47 before the operation + * @flag: one of the BBP_R47_F_* flags + * + * Convenience helper for reading values through BBP R47/R49 pair. + * Takes old value of BBP R47 as @reg, because callers usually have it + * cached already. + * + * Return: value of BBP R49. + */ +static u8 mt7601u_bbp_r47_get(struct mt7601u_dev *dev, u8 reg, u8 flag) +{ + flag |= reg & ~BBP_R47_FLAG; + mt7601u_bbp_wr(dev, 47, flag); + usleep_range(500, 700); + return mt7601u_bbp_rr(dev, 49); +} + +static s8 mt7601u_read_bootup_temp(struct mt7601u_dev *dev) +{ + u8 bbp_val, temp; + u32 rf_bp, rf_set; + int i; + + rf_set = mt7601u_rr(dev, MT_RF_SETTING_0); + rf_bp = mt7601u_rr(dev, MT_RF_BYPASS_0); + + mt7601u_wr(dev, MT_RF_BYPASS_0, 0); + mt7601u_wr(dev, MT_RF_SETTING_0, 0x00000010); + mt7601u_wr(dev, MT_RF_BYPASS_0, 0x00000010); + + bbp_val = mt7601u_bbp_rmw(dev, 47, 0, 0x10); + + mt7601u_bbp_wr(dev, 22, 0x40); + + for (i = 100; i && (bbp_val & 0x10); i--) + bbp_val = mt7601u_bbp_rr(dev, 47); + + temp = mt7601u_bbp_r47_get(dev, bbp_val, BBP_R47_F_TEMP); + + mt7601u_bbp_wr(dev, 22, 0); + + bbp_val = mt7601u_bbp_rr(dev, 21); + bbp_val |= 0x02; + mt7601u_bbp_wr(dev, 21, bbp_val); + bbp_val &= ~0x02; + mt7601u_bbp_wr(dev, 21, bbp_val); + + mt7601u_wr(dev, MT_RF_BYPASS_0, 0); + mt7601u_wr(dev, MT_RF_SETTING_0, rf_set); + mt7601u_wr(dev, MT_RF_BYPASS_0, rf_bp); + + trace_read_temp(dev, temp); + return temp; +} + +static s8 mt7601u_read_temp(struct mt7601u_dev *dev) +{ + int i; + u8 val; + s8 temp; + + val = mt7601u_bbp_rmw(dev, 47, 0x7f, 0x10); + + /* Note: this rarely succeeds, temp can change even if it fails. */ + for (i = 100; i && (val & 0x10); i--) + val = mt7601u_bbp_rr(dev, 47); + + temp = mt7601u_bbp_r47_get(dev, val, BBP_R47_F_TEMP); + + trace_read_temp(dev, temp); + return temp; +} + +static void mt7601u_rxdc_cal(struct mt7601u_dev *dev) +{ + static const struct mt76_reg_pair intro[] = { + { 158, 0x8d }, { 159, 0xfc }, + { 158, 0x8c }, { 159, 0x4c }, + }, outro[] = { + { 158, 0x8d }, { 159, 0xe0 }, + }; + u32 mac_ctrl; + int i, ret; + + mac_ctrl = mt7601u_rr(dev, MT_MAC_SYS_CTRL); + mt7601u_wr(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_RX); + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, + intro, ARRAY_SIZE(intro)); + if (ret) + dev_err(dev->dev, "%s intro failed:%d\n", __func__, ret); + + for (i = 20; i; i--) { + usleep_range(300, 500); + + mt7601u_bbp_wr(dev, 158, 0x8c); + if (mt7601u_bbp_rr(dev, 159) == 0x0c) + break; + } + if (!i) + dev_err(dev->dev, "%s timed out\n", __func__); + + mt7601u_wr(dev, MT_MAC_SYS_CTRL, 0); + + ret = mt7601u_write_reg_pairs(dev, MT_MCU_MEMMAP_BBP, + outro, ARRAY_SIZE(outro)); + if (ret) + dev_err(dev->dev, "%s outro failed:%d\n", __func__, ret); + + mt7601u_wr(dev, MT_MAC_SYS_CTRL, mac_ctrl); +} + +void mt7601u_phy_recalibrate_after_assoc(struct mt7601u_dev *dev) +{ + mt7601u_mcu_calibrate(dev, MCU_CAL_DPD, dev->curr_temp); + + mt7601u_rxdc_cal(dev); +} + +/* Note: function copied from vendor driver */ +static s16 lin2dBd(u16 linear) +{ + short exp = 0; + unsigned int mantisa; + int app, dBd; + + if (WARN_ON(!linear)) + return -10000; + + mantisa = linear; + + exp = fls(mantisa) - 16; + if (exp > 0) + mantisa >>= exp; + else + mantisa <<= abs(exp); + + if (mantisa <= 0xb800) + app = (mantisa + (mantisa >> 3) + (mantisa >> 4) - 0x9600); + else + app = (mantisa - (mantisa >> 3) - (mantisa >> 6) - 0x5a00); + if (app < 0) + app = 0; + + dBd = ((15 + exp) << 15) + app; + dBd = (dBd << 2) + (dBd << 1) + (dBd >> 6) + (dBd >> 7); + dBd = (dBd >> 10); + + return dBd; +} + +static void +mt7601u_set_initial_tssi(struct mt7601u_dev *dev, s16 tssi_db, s16 tssi_hvga_db) +{ + struct tssi_data *d = &dev->ee->tssi_data; + int init_offset; + + init_offset = -((tssi_db * d->slope + d->offset[1]) / 4096) + 10; + + mt76_rmw(dev, MT_TX_ALC_CFG_1, MT_TX_ALC_CFG_1_TEMP_COMP, + int_to_s6(init_offset) & MT_TX_ALC_CFG_1_TEMP_COMP); +} + +static void mt7601u_tssi_dc_gain_cal(struct mt7601u_dev *dev) +{ + u8 rf_vga, rf_mixer, bbp_r47; + int i, j; + s8 res[4]; + s16 tssi_init_db, tssi_init_hvga_db; + + mt7601u_wr(dev, MT_RF_SETTING_0, 0x00000030); + mt7601u_wr(dev, MT_RF_BYPASS_0, 0x000c0030); + mt7601u_wr(dev, MT_MAC_SYS_CTRL, 0); + + mt7601u_bbp_wr(dev, 58, 0); + mt7601u_bbp_wr(dev, 241, 0x2); + mt7601u_bbp_wr(dev, 23, 0x8); + bbp_r47 = mt7601u_bbp_rr(dev, 47); + + /* Set VGA gain */ + rf_vga = mt7601u_rf_rr(dev, 5, 3); + mt7601u_rf_wr(dev, 5, 3, 8); + + /* Mixer disable */ + rf_mixer = mt7601u_rf_rr(dev, 4, 39); + mt7601u_rf_wr(dev, 4, 39, 0); + + for (i = 0; i < 4; i++) { + mt7601u_rf_wr(dev, 4, 39, (i & 1) ? rf_mixer : 0); + + mt7601u_bbp_wr(dev, 23, (i < 2) ? 0x08 : 0x02); + mt7601u_rf_wr(dev, 5, 3, (i < 2) ? 0x08 : 0x11); + + /* BBP TSSI initial and soft reset */ + mt7601u_bbp_wr(dev, 22, 0); + mt7601u_bbp_wr(dev, 244, 0); + + mt7601u_bbp_wr(dev, 21, 1); + udelay(1); + mt7601u_bbp_wr(dev, 21, 0); + + /* TSSI measurement */ + mt7601u_bbp_wr(dev, 47, 0x50); + mt7601u_bbp_wr(dev, (i & 1) ? 244 : 22, (i & 1) ? 0x31 : 0x40); + + for (j = 20; j; j--) + if (!(mt7601u_bbp_rr(dev, 47) & 0x10)) + break; + if (!j) + dev_err(dev->dev, "%s timed out\n", __func__); + + /* TSSI read */ + mt7601u_bbp_wr(dev, 47, 0x40); + res[i] = mt7601u_bbp_rr(dev, 49); + } + + tssi_init_db = lin2dBd((short)res[1] - res[0]); + tssi_init_hvga_db = lin2dBd(((short)res[3] - res[2]) * 4); + dev->tssi_init = res[0]; + dev->tssi_init_hvga = res[2]; + dev->tssi_init_hvga_offset_db = tssi_init_hvga_db - tssi_init_db; + + dev_dbg(dev->dev, + "TSSI_init:%hhx db:%hx hvga:%hhx hvga_db:%hx off_db:%hx\n", + dev->tssi_init, tssi_init_db, dev->tssi_init_hvga, + tssi_init_hvga_db, dev->tssi_init_hvga_offset_db); + + mt7601u_bbp_wr(dev, 22, 0); + mt7601u_bbp_wr(dev, 244, 0); + + mt7601u_bbp_wr(dev, 21, 1); + udelay(1); + mt7601u_bbp_wr(dev, 21, 0); + + mt7601u_wr(dev, MT_RF_BYPASS_0, 0); + mt7601u_wr(dev, MT_RF_SETTING_0, 0); + + mt7601u_rf_wr(dev, 5, 3, rf_vga); + mt7601u_rf_wr(dev, 4, 39, rf_mixer); + mt7601u_bbp_wr(dev, 47, bbp_r47); + + mt7601u_set_initial_tssi(dev, tssi_init_db, tssi_init_hvga_db); +} + +static int mt7601u_temp_comp(struct mt7601u_dev *dev, bool on) +{ + int ret, temp, hi_temp = 400, lo_temp = -200; + + temp = (dev->raw_temp - dev->ee->ref_temp) * MT_EE_TEMPERATURE_SLOPE; + dev->curr_temp = temp; + + /* DPD Calibration */ + if (temp - dev->dpd_temp > 450 || temp - dev->dpd_temp < -450) { + dev->dpd_temp = temp; + + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_DPD, dev->dpd_temp); + if (ret) + return ret; + + mt7601u_vco_cal(dev); + + dev_dbg(dev->dev, "Recalibrate DPD\n"); + } + + /* PLL Lock Protect */ + if (temp < -50 && !dev->pll_lock_protect) { /* < 20C */ + dev->pll_lock_protect = true; + + mt7601u_rf_wr(dev, 4, 4, 6); + mt7601u_rf_clear(dev, 4, 10, 0x30); + + dev_dbg(dev->dev, "PLL lock protect on - too cold\n"); + } else if (temp > 50 && dev->pll_lock_protect) { /* > 30C */ + dev->pll_lock_protect = false; + + mt7601u_rf_wr(dev, 4, 4, 0); + mt7601u_rf_rmw(dev, 4, 10, 0x30, 0x10); + + dev_dbg(dev->dev, "PLL lock protect off\n"); + } + + if (on) { + hi_temp -= 50; + lo_temp -= 50; + } + + /* BBP CR for H, L, N temperature */ + if (temp > hi_temp) + return mt7601u_bbp_temp(dev, MT_TEMP_MODE_HIGH, "high"); + else if (temp > lo_temp) + return mt7601u_bbp_temp(dev, MT_TEMP_MODE_NORMAL, "normal"); + else + return mt7601u_bbp_temp(dev, MT_TEMP_MODE_LOW, "low"); +} + +/* Note: this is used only with TSSI, we can just use trgt_pwr from eeprom. */ +static int mt7601u_current_tx_power(struct mt7601u_dev *dev) +{ + return dev->ee->chan_pwr[dev->chandef.chan->hw_value - 1]; +} + +static bool mt7601u_use_hvga(struct mt7601u_dev *dev) +{ + return !(mt7601u_current_tx_power(dev) > 20); +} + +static s16 +mt7601u_phy_rf_pa_mode_val(struct mt7601u_dev *dev, int phy_mode, int tx_rate) +{ + static const s16 decode_tb[] = { 0, 8847, -5734, -5734 }; + u32 reg; + + switch (phy_mode) { + case MT_PHY_TYPE_OFDM: + tx_rate += 4; + case MT_PHY_TYPE_CCK: + reg = dev->rf_pa_mode[0]; + break; + default: + reg = dev->rf_pa_mode[1]; + break; + } + + return decode_tb[(reg >> (tx_rate * 2)) & 0x3]; +} + +static struct mt7601u_tssi_params +mt7601u_tssi_params_get(struct mt7601u_dev *dev) +{ + static const u8 ofdm_pkt2rate[8] = { 6, 4, 2, 0, 7, 5, 3, 1 }; + static const int static_power[4] = { 0, -49152, -98304, 49152 }; + struct mt7601u_tssi_params p; + u8 bbp_r47, pkt_type, tx_rate; + struct power_per_rate *rate_table; + + bbp_r47 = mt7601u_bbp_rr(dev, 47); + + p.tssi0 = mt7601u_bbp_r47_get(dev, bbp_r47, BBP_R47_F_TSSI); + dev->raw_temp = mt7601u_bbp_r47_get(dev, bbp_r47, BBP_R47_F_TEMP); + pkt_type = mt7601u_bbp_r47_get(dev, bbp_r47, BBP_R47_F_PKT_T); + + p.trgt_power = mt7601u_current_tx_power(dev); + + switch (pkt_type & 0x03) { + case MT_PHY_TYPE_CCK: + tx_rate = (pkt_type >> 4) & 0x03; + rate_table = dev->ee->power_rate_table.cck; + break; + + case MT_PHY_TYPE_OFDM: + tx_rate = ofdm_pkt2rate[(pkt_type >> 4) & 0x07]; + rate_table = dev->ee->power_rate_table.ofdm; + break; + + default: + tx_rate = mt7601u_bbp_r47_get(dev, bbp_r47, BBP_R47_F_TX_RATE); + tx_rate &= 0x7f; + rate_table = dev->ee->power_rate_table.ht; + break; + } + + if (dev->bw == MT_BW_20) + p.trgt_power += rate_table[tx_rate / 2].bw20; + else + p.trgt_power += rate_table[tx_rate / 2].bw40; + + p.trgt_power <<= 12; + + dev_dbg(dev->dev, "tx_rate:%02hhx pwr:%08x\n", tx_rate, p.trgt_power); + + p.trgt_power += mt7601u_phy_rf_pa_mode_val(dev, pkt_type & 0x03, + tx_rate); + + /* Channel 14, cck, bw20 */ + if ((pkt_type & 0x03) == MT_PHY_TYPE_CCK) { + if (mt7601u_bbp_rr(dev, 4) & 0x20) + p.trgt_power += mt7601u_bbp_rr(dev, 178) ? 18022 : 9830; + else + p.trgt_power += mt7601u_bbp_rr(dev, 178) ? 819 : 24576; + } + + p.trgt_power += static_power[mt7601u_bbp_rr(dev, 1) & 0x03]; + + p.trgt_power += dev->ee->tssi_data.tx0_delta_offset; + + dev_dbg(dev->dev, + "tssi:%02hhx t_power:%08x temp:%02hhx pkt_type:%02hhx\n", + p.tssi0, p.trgt_power, dev->raw_temp, pkt_type); + + return p; +} + +static bool mt7601u_tssi_read_ready(struct mt7601u_dev *dev) +{ + return !(mt7601u_bbp_rr(dev, 47) & 0x10); +} + +static int mt7601u_tssi_cal(struct mt7601u_dev *dev) +{ + struct mt7601u_tssi_params params; + int curr_pwr, diff_pwr; + char tssi_offset; + s8 tssi_init; + s16 tssi_m_dc, tssi_db; + bool hvga; + u32 val; + + if (!dev->ee->tssi_enabled) + return 0; + + hvga = mt7601u_use_hvga(dev); + if (!dev->tssi_read_trig) + return mt7601u_mcu_tssi_read_kick(dev, hvga); + + if (!mt7601u_tssi_read_ready(dev)) + return 0; + + params = mt7601u_tssi_params_get(dev); + + tssi_init = (hvga ? dev->tssi_init_hvga : dev->tssi_init); + tssi_m_dc = params.tssi0 - tssi_init; + tssi_db = lin2dBd(tssi_m_dc); + dev_dbg(dev->dev, "tssi dc:%04hx db:%04hx hvga:%d\n", + tssi_m_dc, tssi_db, hvga); + + if (dev->chandef.chan->hw_value < 5) + tssi_offset = dev->ee->tssi_data.offset[0]; + else if (dev->chandef.chan->hw_value < 9) + tssi_offset = dev->ee->tssi_data.offset[1]; + else + tssi_offset = dev->ee->tssi_data.offset[2]; + + if (hvga) + tssi_db -= dev->tssi_init_hvga_offset_db; + + curr_pwr = tssi_db * dev->ee->tssi_data.slope + (tssi_offset << 9); + diff_pwr = params.trgt_power - curr_pwr; + dev_dbg(dev->dev, "Power curr:%08x diff:%08x\n", curr_pwr, diff_pwr); + + if (params.tssi0 > 126 && diff_pwr > 0) { + dev_err(dev->dev, "Error: TSSI upper saturation\n"); + diff_pwr = 0; + } + if (params.tssi0 - tssi_init < 1 && diff_pwr < 0) { + dev_err(dev->dev, "Error: TSSI lower saturation\n"); + diff_pwr = 0; + } + + if ((dev->prev_pwr_diff ^ diff_pwr) < 0 && abs(diff_pwr) < 4096 && + (abs(diff_pwr) > abs(dev->prev_pwr_diff) || + (diff_pwr > 0 && diff_pwr == -dev->prev_pwr_diff))) + diff_pwr = 0; + else + dev->prev_pwr_diff = diff_pwr; + + diff_pwr += (diff_pwr > 0) ? 2048 : -2048; + diff_pwr /= 4096; + + dev_dbg(dev->dev, "final diff: %08x\n", diff_pwr); + + val = mt7601u_rr(dev, MT_TX_ALC_CFG_1); + curr_pwr = s6_to_int(MT76_GET(MT_TX_ALC_CFG_1_TEMP_COMP, val)); + diff_pwr += curr_pwr; + val = (val & ~MT_TX_ALC_CFG_1_TEMP_COMP) | int_to_s6(diff_pwr); + mt7601u_wr(dev, MT_TX_ALC_CFG_1, val); + + return mt7601u_mcu_tssi_read_kick(dev, hvga); +} + +static u8 mt7601u_agc_default(struct mt7601u_dev *dev) +{ + return (dev->ee->lna_gain - 8) * 2 + 0x34; +} + +static void mt7601u_agc_reset(struct mt7601u_dev *dev) +{ + u8 agc = mt7601u_agc_default(dev); + + mt7601u_bbp_wr(dev, 66, agc); +} + +void mt7601u_agc_save(struct mt7601u_dev *dev) +{ + dev->agc_save = mt7601u_bbp_rr(dev, 66); +} + +void mt7601u_agc_restore(struct mt7601u_dev *dev) +{ + mt7601u_bbp_wr(dev, 66, dev->agc_save); +} + +static void mt7601u_agc_tune(struct mt7601u_dev *dev) +{ + u8 val = mt7601u_agc_default(dev); + + if (test_bit(MT7601U_STATE_SCANNING, &dev->state)) + return; + + /* Note: only in STA mode and not dozing; perhaps do this only if + * there is enough rssi updates since last run? + * Rssi updates are only on beacons and U2M so should work... + */ + spin_lock_bh(&dev->con_mon_lock); + if (dev->avg_rssi <= -70) + val -= 0x20; + else if (dev->avg_rssi <= -60) + val -= 0x10; + spin_unlock_bh(&dev->con_mon_lock); + + if (val != mt7601u_bbp_rr(dev, 66)) + mt7601u_bbp_wr(dev, 66, val); + + /* TODO: also if lost a lot of beacons try resetting + * (see RTMPSetAGCInitValue() call in mlme.c). + */ +} + +static void mt7601u_phy_calibrate(struct work_struct *work) +{ + struct mt7601u_dev *dev = container_of(work, struct mt7601u_dev, + cal_work.work); + + mt7601u_agc_tune(dev); + mt7601u_tssi_cal(dev); + /* If TSSI calibration was run it already updated temperature. */ + if (!dev->ee->tssi_enabled) + dev->raw_temp = mt7601u_read_temp(dev); + mt7601u_temp_comp(dev, true); /* TODO: find right value for @on */ + + ieee80211_queue_delayed_work(dev->hw, &dev->cal_work, + MT_CALIBRATE_INTERVAL); +} + +static unsigned long +__mt7601u_phy_freq_cal(struct mt7601u_dev *dev, s8 last_offset, u8 phy_mode) +{ + u8 activate_threshold, deactivate_threshold; + + trace_freq_cal_offset(dev, phy_mode, last_offset); + + /* No beacons received - reschedule soon */ + if (last_offset == MT_FREQ_OFFSET_INVALID) + return MT_FREQ_CAL_ADJ_INTERVAL; + + switch (phy_mode) { + case MT_PHY_TYPE_CCK: + activate_threshold = 19; + deactivate_threshold = 5; + break; + case MT_PHY_TYPE_OFDM: + activate_threshold = 102; + deactivate_threshold = 32; + break; + case MT_PHY_TYPE_HT: + case MT_PHY_TYPE_HT_GF: + activate_threshold = 82; + deactivate_threshold = 20; + break; + default: + WARN_ON(1); + return MT_FREQ_CAL_CHECK_INTERVAL; + } + + if (abs(last_offset) >= activate_threshold) + dev->freq_cal.adjusting = true; + else if (abs(last_offset) <= deactivate_threshold) + dev->freq_cal.adjusting = false; + + if (!dev->freq_cal.adjusting) + return MT_FREQ_CAL_CHECK_INTERVAL; + + if (last_offset > deactivate_threshold) { + if (dev->freq_cal.freq > 0) + dev->freq_cal.freq--; + else + dev->freq_cal.adjusting = false; + } else if (last_offset < -deactivate_threshold) { + if (dev->freq_cal.freq < 0xbf) + dev->freq_cal.freq++; + else + dev->freq_cal.adjusting = false; + } + + trace_freq_cal_adjust(dev, dev->freq_cal.freq); + mt7601u_rf_wr(dev, 0, 12, dev->freq_cal.freq); + mt7601u_vco_cal(dev); + + return dev->freq_cal.adjusting ? MT_FREQ_CAL_ADJ_INTERVAL : + MT_FREQ_CAL_CHECK_INTERVAL; +} + +static void mt7601u_phy_freq_cal(struct work_struct *work) +{ + struct mt7601u_dev *dev = container_of(work, struct mt7601u_dev, + freq_cal.work.work); + s8 last_offset; + u8 phy_mode; + unsigned long delay; + + spin_lock_bh(&dev->con_mon_lock); + last_offset = dev->bcn_freq_off; + phy_mode = dev->bcn_phy_mode; + spin_unlock_bh(&dev->con_mon_lock); + + delay = __mt7601u_phy_freq_cal(dev, last_offset, phy_mode); + ieee80211_queue_delayed_work(dev->hw, &dev->freq_cal.work, delay); + + spin_lock_bh(&dev->con_mon_lock); + dev->bcn_freq_off = MT_FREQ_OFFSET_INVALID; + spin_unlock_bh(&dev->con_mon_lock); +} + +void mt7601u_phy_con_cal_onoff(struct mt7601u_dev *dev, + struct ieee80211_bss_conf *info) +{ + if (!info->assoc) + cancel_delayed_work_sync(&dev->freq_cal.work); + + /* Start/stop collecting beacon data */ + spin_lock_bh(&dev->con_mon_lock); + ether_addr_copy(dev->ap_bssid, info->bssid); + dev->avg_rssi = 0; + dev->bcn_freq_off = MT_FREQ_OFFSET_INVALID; + spin_unlock_bh(&dev->con_mon_lock); + + dev->freq_cal.freq = dev->ee->rf_freq_off; + dev->freq_cal.enabled = info->assoc; + dev->freq_cal.adjusting = false; + + if (info->assoc) + ieee80211_queue_delayed_work(dev->hw, &dev->freq_cal.work, + MT_FREQ_CAL_INIT_DELAY); +} + +static int mt7601u_init_cal(struct mt7601u_dev *dev) +{ + u32 mac_ctrl; + int ret; + + dev->raw_temp = mt7601u_read_bootup_temp(dev); + dev->curr_temp = (dev->raw_temp - dev->ee->ref_temp) * + MT_EE_TEMPERATURE_SLOPE; + dev->dpd_temp = dev->curr_temp; + + mac_ctrl = mt7601u_rr(dev, MT_MAC_SYS_CTRL); + + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_R, 0); + if (ret) + return ret; + + ret = mt7601u_rf_rr(dev, 0, 4); + if (ret < 0) + return ret; + ret |= 0x80; + ret = mt7601u_rf_wr(dev, 0, 4, ret); + if (ret) + return ret; + msleep(2); + + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_TXDCOC, 0); + if (ret) + return ret; + + mt7601u_rxdc_cal(dev); + + ret = mt7601u_set_bw_filter(dev, true); + if (ret) + return ret; + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_LOFT, 0); + if (ret) + return ret; + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_TXIQ, 0); + if (ret) + return ret; + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_RXIQ, 0); + if (ret) + return ret; + ret = mt7601u_mcu_calibrate(dev, MCU_CAL_DPD, dev->dpd_temp); + if (ret) + return ret; + + mt7601u_rxdc_cal(dev); + + mt7601u_tssi_dc_gain_cal(dev); + + mt7601u_wr(dev, MT_MAC_SYS_CTRL, mac_ctrl); + + mt7601u_temp_comp(dev, true); + + return 0; +} + +int mt7601u_bbp_set_bw(struct mt7601u_dev *dev, int bw) +{ + u32 val, old; + + if (bw == dev->bw) { + /* Vendor driver does the rmc even when no change is needed. */ + mt7601u_bbp_rmc(dev, 4, 0x18, bw == MT_BW_20 ? 0 : 0x10); + + return 0; + } + dev->bw = bw; + + /* Stop MAC for the time of bw change */ + old = mt7601u_rr(dev, MT_MAC_SYS_CTRL); + val = old & ~(MT_MAC_SYS_CTRL_ENABLE_TX | MT_MAC_SYS_CTRL_ENABLE_RX); + mt7601u_wr(dev, MT_MAC_SYS_CTRL, val); + mt76_poll(dev, MT_MAC_STATUS, MT_MAC_STATUS_TX | MT_MAC_STATUS_RX, + 0, 500000); + + mt7601u_bbp_rmc(dev, 4, 0x18, bw == MT_BW_20 ? 0 : 0x10); + + mt7601u_wr(dev, MT_MAC_SYS_CTRL, old); + + return mt7601u_load_bbp_temp_table_bw(dev); +} + +/** + * mt7601u_set_rx_path - set rx path in BBP + * @dev: pointer to adapter structure + * @path: rx path to set values are 0-based + */ +void mt7601u_set_rx_path(struct mt7601u_dev *dev, u8 path) +{ + mt7601u_bbp_rmw(dev, 3, 0x18, path << 3); +} + +/** + * mt7601u_set_tx_dac - set which tx DAC to use + * @dev: pointer to adapter structure + * @path: DAC index, values are 0-based + */ +void mt7601u_set_tx_dac(struct mt7601u_dev *dev, u8 dac) +{ + mt7601u_bbp_rmc(dev, 1, 0x18, dac << 3); +} + +int mt7601u_phy_init(struct mt7601u_dev *dev) +{ + int ret; + + dev->rf_pa_mode[0] = mt7601u_rr(dev, MT_RF_PA_MODE_CFG0); + dev->rf_pa_mode[1] = mt7601u_rr(dev, MT_RF_PA_MODE_CFG1); + + ret = mt7601u_rf_wr(dev, 0, 12, dev->ee->rf_freq_off); + if (ret) + return ret; + ret = mt7601u_write_reg_pairs(dev, 0, rf_central, + ARRAY_SIZE(rf_central)); + if (ret) + return ret; + ret = mt7601u_write_reg_pairs(dev, 0, rf_channel, + ARRAY_SIZE(rf_channel)); + if (ret) + return ret; + ret = mt7601u_write_reg_pairs(dev, 0, rf_vga, ARRAY_SIZE(rf_vga)); + if (ret) + return ret; + + ret = mt7601u_init_cal(dev); + if (ret) + return ret; + + dev->prev_pwr_diff = 100; + + INIT_DELAYED_WORK(&dev->cal_work, mt7601u_phy_calibrate); + INIT_DELAYED_WORK(&dev->freq_cal.work, mt7601u_phy_freq_cal); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/regs.h b/drivers/net/wireless/mediatek/mt7601u/regs.h new file mode 100644 index 0000000000000..afd8978e83fac --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/regs.h @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT76_REGS_H +#define __MT76_REGS_H + +#include + +#ifndef GENMASK +#define GENMASK(h, l) (((U32_C(1) << ((h) - (l) + 1)) - 1) << (l)) +#endif + +#define MT_ASIC_VERSION 0x0000 + +#define MT76XX_REV_E3 0x22 +#define MT76XX_REV_E4 0x33 + +#define MT_CMB_CTRL 0x0020 +#define MT_CMB_CTRL_XTAL_RDY BIT(22) +#define MT_CMB_CTRL_PLL_LD BIT(23) + +#define MT_EFUSE_CTRL 0x0024 +#define MT_EFUSE_CTRL_AOUT GENMASK(5, 0) +#define MT_EFUSE_CTRL_MODE GENMASK(7, 6) +#define MT_EFUSE_CTRL_LDO_OFF_TIME GENMASK(13, 8) +#define MT_EFUSE_CTRL_LDO_ON_TIME GENMASK(15, 14) +#define MT_EFUSE_CTRL_AIN GENMASK(25, 16) +#define MT_EFUSE_CTRL_KICK BIT(30) +#define MT_EFUSE_CTRL_SEL BIT(31) + +#define MT_EFUSE_DATA_BASE 0x0028 +#define MT_EFUSE_DATA(_n) (MT_EFUSE_DATA_BASE + ((_n) << 2)) + +#define MT_COEXCFG0 0x0040 +#define MT_COEXCFG0_COEX_EN BIT(0) + +#define MT_WLAN_FUN_CTRL 0x0080 +#define MT_WLAN_FUN_CTRL_WLAN_EN BIT(0) +#define MT_WLAN_FUN_CTRL_WLAN_CLK_EN BIT(1) +#define MT_WLAN_FUN_CTRL_WLAN_RESET_RF BIT(2) + +#define MT_WLAN_FUN_CTRL_WLAN_RESET BIT(3) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_CSR_F20M_CKEN BIT(3) /* MT76x2 */ + +#define MT_WLAN_FUN_CTRL_PCIE_CLK_REQ BIT(4) +#define MT_WLAN_FUN_CTRL_FRC_WL_ANT_SEL BIT(5) +#define MT_WLAN_FUN_CTRL_INV_ANT_SEL BIT(6) +#define MT_WLAN_FUN_CTRL_WAKE_HOST BIT(7) + +#define MT_WLAN_FUN_CTRL_THERM_RST BIT(8) /* MT76x2 */ +#define MT_WLAN_FUN_CTRL_THERM_CKEN BIT(9) /* MT76x2 */ + +#define MT_WLAN_FUN_CTRL_GPIO_IN GENMASK(15, 8) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_GPIO_OUT GENMASK(23, 16) /* MT76x0 */ +#define MT_WLAN_FUN_CTRL_GPIO_OUT_EN GENMASK(31, 24) /* MT76x0 */ + +#define MT_XO_CTRL0 0x0100 +#define MT_XO_CTRL1 0x0104 +#define MT_XO_CTRL2 0x0108 +#define MT_XO_CTRL3 0x010c +#define MT_XO_CTRL4 0x0110 + +#define MT_XO_CTRL5 0x0114 +#define MT_XO_CTRL5_C2_VAL GENMASK(14, 8) + +#define MT_XO_CTRL6 0x0118 +#define MT_XO_CTRL6_C2_CTRL GENMASK(14, 8) + +#define MT_XO_CTRL7 0x011c + +#define MT_WLAN_MTC_CTRL 0x10148 +#define MT_WLAN_MTC_CTRL_MTCMOS_PWR_UP BIT(0) +#define MT_WLAN_MTC_CTRL_PWR_ACK BIT(12) +#define MT_WLAN_MTC_CTRL_PWR_ACK_S BIT(13) +#define MT_WLAN_MTC_CTRL_BBP_MEM_PD GENMASK(19, 16) +#define MT_WLAN_MTC_CTRL_PBF_MEM_PD BIT(20) +#define MT_WLAN_MTC_CTRL_FCE_MEM_PD BIT(21) +#define MT_WLAN_MTC_CTRL_TSO_MEM_PD BIT(22) +#define MT_WLAN_MTC_CTRL_BBP_MEM_RB BIT(24) +#define MT_WLAN_MTC_CTRL_PBF_MEM_RB BIT(25) +#define MT_WLAN_MTC_CTRL_FCE_MEM_RB BIT(26) +#define MT_WLAN_MTC_CTRL_TSO_MEM_RB BIT(27) +#define MT_WLAN_MTC_CTRL_STATE_UP BIT(28) + +#define MT_INT_SOURCE_CSR 0x0200 +#define MT_INT_MASK_CSR 0x0204 + +#define MT_INT_RX_DONE(_n) BIT(_n) +#define MT_INT_RX_DONE_ALL GENMASK(1, 0) +#define MT_INT_TX_DONE_ALL GENMASK(13, 4) +#define MT_INT_TX_DONE(_n) BIT(_n + 4) +#define MT_INT_RX_COHERENT BIT(16) +#define MT_INT_TX_COHERENT BIT(17) +#define MT_INT_ANY_COHERENT BIT(18) +#define MT_INT_MCU_CMD BIT(19) +#define MT_INT_TBTT BIT(20) +#define MT_INT_PRE_TBTT BIT(21) +#define MT_INT_TX_STAT BIT(22) +#define MT_INT_AUTO_WAKEUP BIT(23) +#define MT_INT_GPTIMER BIT(24) +#define MT_INT_RXDELAYINT BIT(26) +#define MT_INT_TXDELAYINT BIT(27) + +#define MT_WPDMA_GLO_CFG 0x0208 +#define MT_WPDMA_GLO_CFG_TX_DMA_EN BIT(0) +#define MT_WPDMA_GLO_CFG_TX_DMA_BUSY BIT(1) +#define MT_WPDMA_GLO_CFG_RX_DMA_EN BIT(2) +#define MT_WPDMA_GLO_CFG_RX_DMA_BUSY BIT(3) +#define MT_WPDMA_GLO_CFG_DMA_BURST_SIZE GENMASK(5, 4) +#define MT_WPDMA_GLO_CFG_TX_WRITEBACK_DONE BIT(6) +#define MT_WPDMA_GLO_CFG_BIG_ENDIAN BIT(7) +#define MT_WPDMA_GLO_CFG_HDR_SEG_LEN GENMASK(15, 8) +#define MT_WPDMA_GLO_CFG_CLK_GATE_DIS BIT(30) +#define MT_WPDMA_GLO_CFG_RX_2B_OFFSET BIT(31) + +#define MT_WPDMA_RST_IDX 0x020c + +#define MT_WPDMA_DELAY_INT_CFG 0x0210 + +#define MT_WMM_AIFSN 0x0214 +#define MT_WMM_AIFSN_MASK GENMASK(3, 0) +#define MT_WMM_AIFSN_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_CWMIN 0x0218 +#define MT_WMM_CWMIN_MASK GENMASK(3, 0) +#define MT_WMM_CWMIN_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_CWMAX 0x021c +#define MT_WMM_CWMAX_MASK GENMASK(3, 0) +#define MT_WMM_CWMAX_SHIFT(_n) ((_n) * 4) + +#define MT_WMM_TXOP_BASE 0x0220 +#define MT_WMM_TXOP(_n) (MT_WMM_TXOP_BASE + (((_n) / 2) << 2)) +#define MT_WMM_TXOP_SHIFT(_n) ((_n & 1) * 16) +#define MT_WMM_TXOP_MASK GENMASK(15, 0) + +#define MT_FCE_DMA_ADDR 0x0230 +#define MT_FCE_DMA_LEN 0x0234 + +#define MT_USB_DMA_CFG 0x238 +#define MT_USB_DMA_CFG_RX_BULK_AGG_TOUT GENMASK(7, 0) +#define MT_USB_DMA_CFG_RX_BULK_AGG_LMT GENMASK(15, 8) +#define MT_USB_DMA_CFG_PHY_CLR BIT(16) +#define MT_USB_DMA_CFG_TX_CLR BIT(19) +#define MT_USB_DMA_CFG_TXOP_HALT BIT(20) +#define MT_USB_DMA_CFG_RX_BULK_AGG_EN BIT(21) +#define MT_USB_DMA_CFG_RX_BULK_EN BIT(22) +#define MT_USB_DMA_CFG_TX_BULK_EN BIT(23) +#define MT_USB_DMA_CFG_UDMA_RX_WL_DROP BIT(25) +#define MT_USB_DMA_CFG_EP_OUT_VALID GENMASK(29, 27) +#define MT_USB_DMA_CFG_RX_BUSY BIT(30) +#define MT_USB_DMA_CFG_TX_BUSY BIT(31) + +#define MT_TSO_CTRL 0x0250 +#define MT_HEADER_TRANS_CTRL_REG 0x0260 + +#define MT_US_CYC_CFG 0x02a4 +#define MT_US_CYC_CNT GENMASK(7, 0) + +#define MT_TX_RING_BASE 0x0300 +#define MT_RX_RING_BASE 0x03c0 +#define MT_RING_SIZE 0x10 + +#define MT_TX_HW_QUEUE_MCU 8 +#define MT_TX_HW_QUEUE_MGMT 9 + +#define MT_PBF_SYS_CTRL 0x0400 +#define MT_PBF_SYS_CTRL_MCU_RESET BIT(0) +#define MT_PBF_SYS_CTRL_DMA_RESET BIT(1) +#define MT_PBF_SYS_CTRL_MAC_RESET BIT(2) +#define MT_PBF_SYS_CTRL_PBF_RESET BIT(3) +#define MT_PBF_SYS_CTRL_ASY_RESET BIT(4) + +#define MT_PBF_CFG 0x0404 +#define MT_PBF_CFG_TX0Q_EN BIT(0) +#define MT_PBF_CFG_TX1Q_EN BIT(1) +#define MT_PBF_CFG_TX2Q_EN BIT(2) +#define MT_PBF_CFG_TX3Q_EN BIT(3) +#define MT_PBF_CFG_RX0Q_EN BIT(4) +#define MT_PBF_CFG_RX_DROP_EN BIT(8) + +#define MT_PBF_TX_MAX_PCNT 0x0408 +#define MT_PBF_RX_MAX_PCNT 0x040c + +#define MT_BCN_OFFSET_BASE 0x041c +#define MT_BCN_OFFSET(_n) (MT_BCN_OFFSET_BASE + ((_n) << 2)) + +#define MT_RF_CSR_CFG 0x0500 +#define MT_RF_CSR_CFG_DATA GENMASK(7, 0) +#define MT_RF_CSR_CFG_REG_ID GENMASK(13, 8) +#define MT_RF_CSR_CFG_REG_BANK GENMASK(17, 14) +#define MT_RF_CSR_CFG_WR BIT(30) +#define MT_RF_CSR_CFG_KICK BIT(31) + +#define MT_RF_BYPASS_0 0x0504 +#define MT_RF_BYPASS_1 0x0508 +#define MT_RF_SETTING_0 0x050c + +#define MT_RF_DATA_WRITE 0x0524 + +#define MT_RF_CTRL 0x0528 +#define MT_RF_CTRL_ADDR GENMASK(11, 0) +#define MT_RF_CTRL_WRITE BIT(12) +#define MT_RF_CTRL_BUSY BIT(13) +#define MT_RF_CTRL_IDX BIT(16) + +#define MT_RF_DATA_READ 0x052c + +#define MT_FCE_PSE_CTRL 0x0800 +#define MT_FCE_PARAMETERS 0x0804 +#define MT_FCE_CSO 0x0808 + +#define MT_FCE_L2_STUFF 0x080c +#define MT_FCE_L2_STUFF_HT_L2_EN BIT(0) +#define MT_FCE_L2_STUFF_QOS_L2_EN BIT(1) +#define MT_FCE_L2_STUFF_RX_STUFF_EN BIT(2) +#define MT_FCE_L2_STUFF_TX_STUFF_EN BIT(3) +#define MT_FCE_L2_STUFF_WR_MPDU_LEN_EN BIT(4) +#define MT_FCE_L2_STUFF_MVINV_BSWAP BIT(5) +#define MT_FCE_L2_STUFF_TS_CMD_QSEL_EN GENMASK(15, 8) +#define MT_FCE_L2_STUFF_TS_LEN_EN GENMASK(23, 16) +#define MT_FCE_L2_STUFF_OTHER_PORT GENMASK(25, 24) + +#define MT_FCE_WLAN_FLOW_CONTROL1 0x0824 + +#define MT_TX_CPU_FROM_FCE_BASE_PTR 0x09a0 +#define MT_TX_CPU_FROM_FCE_MAX_COUNT 0x09a4 +#define MT_TX_CPU_FROM_FCE_CPU_DESC_IDX 0x09a8 + +#define MT_FCE_PDMA_GLOBAL_CONF 0x09c4 + +#define MT_PAUSE_ENABLE_CONTROL1 0x0a38 + +#define MT_FCE_SKIP_FS 0x0a6c + +#define MT_MAC_CSR0 0x1000 + +#define MT_MAC_SYS_CTRL 0x1004 +#define MT_MAC_SYS_CTRL_RESET_CSR BIT(0) +#define MT_MAC_SYS_CTRL_RESET_BBP BIT(1) +#define MT_MAC_SYS_CTRL_ENABLE_TX BIT(2) +#define MT_MAC_SYS_CTRL_ENABLE_RX BIT(3) + +#define MT_MAC_ADDR_DW0 0x1008 +#define MT_MAC_ADDR_DW1 0x100c +#define MT_MAC_ADDR_DW1_U2ME_MASK GENMASK(23, 16) + +#define MT_MAC_BSSID_DW0 0x1010 +#define MT_MAC_BSSID_DW1 0x1014 +#define MT_MAC_BSSID_DW1_ADDR GENMASK(15, 0) +#define MT_MAC_BSSID_DW1_MBSS_MODE GENMASK(17, 16) +#define MT_MAC_BSSID_DW1_MBEACON_N GENMASK(20, 18) +#define MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT BIT(21) +#define MT_MAC_BSSID_DW1_MBSS_MODE_B2 BIT(22) +#define MT_MAC_BSSID_DW1_MBEACON_N_B3 BIT(23) +#define MT_MAC_BSSID_DW1_MBSS_IDX_BYTE GENMASK(26, 24) + +#define MT_MAX_LEN_CFG 0x1018 +#define MT_MAX_LEN_CFG_AMPDU GENMASK(13, 12) + +#define MT_BBP_CSR_CFG 0x101c +#define MT_BBP_CSR_CFG_VAL GENMASK(7, 0) +#define MT_BBP_CSR_CFG_REG_NUM GENMASK(15, 8) +#define MT_BBP_CSR_CFG_READ BIT(16) +#define MT_BBP_CSR_CFG_BUSY BIT(17) +#define MT_BBP_CSR_CFG_PAR_DUR BIT(18) +#define MT_BBP_CSR_CFG_RW_MODE BIT(19) + +#define MT_AMPDU_MAX_LEN_20M1S 0x1030 +#define MT_AMPDU_MAX_LEN_20M2S 0x1034 +#define MT_AMPDU_MAX_LEN_40M1S 0x1038 +#define MT_AMPDU_MAX_LEN_40M2S 0x103c +#define MT_AMPDU_MAX_LEN 0x1040 + +#define MT_WCID_DROP_BASE 0x106c +#define MT_WCID_DROP(_n) (MT_WCID_DROP_BASE + ((_n) >> 5) * 4) +#define MT_WCID_DROP_MASK(_n) BIT((_n) % 32) + +#define MT_BCN_BYPASS_MASK 0x108c + +#define MT_MAC_APC_BSSID_BASE 0x1090 +#define MT_MAC_APC_BSSID_L(_n) (MT_MAC_APC_BSSID_BASE + ((_n) * 8)) +#define MT_MAC_APC_BSSID_H(_n) (MT_MAC_APC_BSSID_BASE + ((_n) * 8 + 4)) +#define MT_MAC_APC_BSSID_H_ADDR GENMASK(15, 0) +#define MT_MAC_APC_BSSID0_H_EN BIT(16) + +#define MT_XIFS_TIME_CFG 0x1100 +#define MT_XIFS_TIME_CFG_CCK_SIFS GENMASK(7, 0) +#define MT_XIFS_TIME_CFG_OFDM_SIFS GENMASK(15, 8) +#define MT_XIFS_TIME_CFG_OFDM_XIFS GENMASK(19, 16) +#define MT_XIFS_TIME_CFG_EIFS GENMASK(28, 20) +#define MT_XIFS_TIME_CFG_BB_RXEND_EN BIT(29) + +#define MT_BKOFF_SLOT_CFG 0x1104 +#define MT_BKOFF_SLOT_CFG_SLOTTIME GENMASK(7, 0) +#define MT_BKOFF_SLOT_CFG_CC_DELAY GENMASK(11, 8) + +#define MT_BEACON_TIME_CFG 0x1114 +#define MT_BEACON_TIME_CFG_INTVAL GENMASK(15, 0) +#define MT_BEACON_TIME_CFG_TIMER_EN BIT(16) +#define MT_BEACON_TIME_CFG_SYNC_MODE GENMASK(18, 17) +#define MT_BEACON_TIME_CFG_TBTT_EN BIT(19) +#define MT_BEACON_TIME_CFG_BEACON_TX BIT(20) +#define MT_BEACON_TIME_CFG_TSF_COMP GENMASK(31, 24) + +#define MT_TBTT_SYNC_CFG 0x1118 +#define MT_TBTT_TIMER_CFG 0x1124 + +#define MT_INT_TIMER_CFG 0x1128 +#define MT_INT_TIMER_CFG_PRE_TBTT GENMASK(15, 0) +#define MT_INT_TIMER_CFG_GP_TIMER GENMASK(31, 16) + +#define MT_INT_TIMER_EN 0x112c +#define MT_INT_TIMER_EN_PRE_TBTT_EN BIT(0) +#define MT_INT_TIMER_EN_GP_TIMER_EN BIT(1) + +#define MT_MAC_STATUS 0x1200 +#define MT_MAC_STATUS_TX BIT(0) +#define MT_MAC_STATUS_RX BIT(1) + +#define MT_PWR_PIN_CFG 0x1204 +#define MT_AUX_CLK_CFG 0x120c + +#define MT_BB_PA_MODE_CFG0 0x1214 +#define MT_BB_PA_MODE_CFG1 0x1218 +#define MT_RF_PA_MODE_CFG0 0x121c +#define MT_RF_PA_MODE_CFG1 0x1220 + +#define MT_RF_PA_MODE_ADJ0 0x1228 +#define MT_RF_PA_MODE_ADJ1 0x122c + +#define MT_DACCLK_EN_DLY_CFG 0x1264 + +#define MT_EDCA_CFG_BASE 0x1300 +#define MT_EDCA_CFG_AC(_n) (MT_EDCA_CFG_BASE + ((_n) << 2)) +#define MT_EDCA_CFG_TXOP GENMASK(7, 0) +#define MT_EDCA_CFG_AIFSN GENMASK(11, 8) +#define MT_EDCA_CFG_CWMIN GENMASK(15, 12) +#define MT_EDCA_CFG_CWMAX GENMASK(19, 16) + +#define MT_TX_PWR_CFG_0 0x1314 +#define MT_TX_PWR_CFG_1 0x1318 +#define MT_TX_PWR_CFG_2 0x131c +#define MT_TX_PWR_CFG_3 0x1320 +#define MT_TX_PWR_CFG_4 0x1324 + +#define MT_TX_BAND_CFG 0x132c +#define MT_TX_BAND_CFG_UPPER_40M BIT(0) +#define MT_TX_BAND_CFG_5G BIT(1) +#define MT_TX_BAND_CFG_2G BIT(2) + +#define MT_HT_FBK_TO_LEGACY 0x1384 +#define MT_TX_MPDU_ADJ_INT 0x1388 + +#define MT_TX_PWR_CFG_7 0x13d4 +#define MT_TX_PWR_CFG_8 0x13d8 +#define MT_TX_PWR_CFG_9 0x13dc + +#define MT_TX_SW_CFG0 0x1330 +#define MT_TX_SW_CFG1 0x1334 +#define MT_TX_SW_CFG2 0x1338 + +#define MT_TXOP_CTRL_CFG 0x1340 +#define MT_TXOP_TRUN_EN GENMASK(5, 0) +#define MT_TXOP_EXT_CCA_DLY GENMASK(15, 8) +#define MT_TXOP_CTRL + +#define MT_TX_RTS_CFG 0x1344 +#define MT_TX_RTS_CFG_RETRY_LIMIT GENMASK(7, 0) +#define MT_TX_RTS_CFG_THRESH GENMASK(23, 8) +#define MT_TX_RTS_FALLBACK BIT(24) + +#define MT_TX_TIMEOUT_CFG 0x1348 +#define MT_TX_RETRY_CFG 0x134c +#define MT_TX_LINK_CFG 0x1350 +#define MT_HT_FBK_CFG0 0x1354 +#define MT_HT_FBK_CFG1 0x1358 +#define MT_LG_FBK_CFG0 0x135c +#define MT_LG_FBK_CFG1 0x1360 + +#define MT_CCK_PROT_CFG 0x1364 +#define MT_OFDM_PROT_CFG 0x1368 +#define MT_MM20_PROT_CFG 0x136c +#define MT_MM40_PROT_CFG 0x1370 +#define MT_GF20_PROT_CFG 0x1374 +#define MT_GF40_PROT_CFG 0x1378 + +#define MT_PROT_RATE GENMASK(15, 0) +#define MT_PROT_CTRL_RTS_CTS BIT(16) +#define MT_PROT_CTRL_CTS2SELF BIT(17) +#define MT_PROT_NAV_SHORT BIT(18) +#define MT_PROT_NAV_LONG BIT(19) +#define MT_PROT_TXOP_ALLOW_CCK BIT(20) +#define MT_PROT_TXOP_ALLOW_OFDM BIT(21) +#define MT_PROT_TXOP_ALLOW_MM20 BIT(22) +#define MT_PROT_TXOP_ALLOW_MM40 BIT(23) +#define MT_PROT_TXOP_ALLOW_GF20 BIT(24) +#define MT_PROT_TXOP_ALLOW_GF40 BIT(25) +#define MT_PROT_RTS_THR_EN BIT(26) +#define MT_PROT_RATE_CCK_11 0x0003 +#define MT_PROT_RATE_OFDM_6 0x4000 +#define MT_PROT_RATE_OFDM_24 0x4004 +#define MT_PROT_RATE_DUP_OFDM_24 0x4084 +#define MT_PROT_TXOP_ALLOW_ALL GENMASK(25, 20) +#define MT_PROT_TXOP_ALLOW_BW20 (MT_PROT_TXOP_ALLOW_ALL & \ + ~MT_PROT_TXOP_ALLOW_MM40 & \ + ~MT_PROT_TXOP_ALLOW_GF40) + +#define MT_EXP_ACK_TIME 0x1380 + +#define MT_TX_PWR_CFG_0_EXT 0x1390 +#define MT_TX_PWR_CFG_1_EXT 0x1394 + +#define MT_TX_FBK_LIMIT 0x1398 +#define MT_TX_FBK_LIMIT_MPDU_FBK GENMASK(7, 0) +#define MT_TX_FBK_LIMIT_AMPDU_FBK GENMASK(15, 8) +#define MT_TX_FBK_LIMIT_MPDU_UP_CLEAR BIT(16) +#define MT_TX_FBK_LIMIT_AMPDU_UP_CLEAR BIT(17) +#define MT_TX_FBK_LIMIT_RATE_LUT BIT(18) + +#define MT_TX0_RF_GAIN_CORR 0x13a0 +#define MT_TX1_RF_GAIN_CORR 0x13a4 +#define MT_TX0_RF_GAIN_ATTEN 0x13a8 + +#define MT_TX_ALC_CFG_0 0x13b0 +#define MT_TX_ALC_CFG_0_CH_INIT_0 GENMASK(5, 0) +#define MT_TX_ALC_CFG_0_CH_INIT_1 GENMASK(13, 8) +#define MT_TX_ALC_CFG_0_LIMIT_0 GENMASK(21, 16) +#define MT_TX_ALC_CFG_0_LIMIT_1 GENMASK(29, 24) + +#define MT_TX_ALC_CFG_1 0x13b4 +#define MT_TX_ALC_CFG_1_TEMP_COMP GENMASK(5, 0) + +#define MT_TX_ALC_CFG_2 0x13a8 +#define MT_TX_ALC_CFG_2_TEMP_COMP GENMASK(5, 0) + +#define MT_TX0_BB_GAIN_ATTEN 0x13c0 + +#define MT_TX_ALC_VGA3 0x13c8 + +#define MT_TX_PROT_CFG6 0x13e0 +#define MT_TX_PROT_CFG7 0x13e4 +#define MT_TX_PROT_CFG8 0x13e8 + +#define MT_PIFS_TX_CFG 0x13ec + +#define MT_RX_FILTR_CFG 0x1400 + +#define MT_RX_FILTR_CFG_CRC_ERR BIT(0) +#define MT_RX_FILTR_CFG_PHY_ERR BIT(1) +#define MT_RX_FILTR_CFG_PROMISC BIT(2) +#define MT_RX_FILTR_CFG_OTHER_BSS BIT(3) +#define MT_RX_FILTR_CFG_VER_ERR BIT(4) +#define MT_RX_FILTR_CFG_MCAST BIT(5) +#define MT_RX_FILTR_CFG_BCAST BIT(6) +#define MT_RX_FILTR_CFG_DUP BIT(7) +#define MT_RX_FILTR_CFG_CFACK BIT(8) +#define MT_RX_FILTR_CFG_CFEND BIT(9) +#define MT_RX_FILTR_CFG_ACK BIT(10) +#define MT_RX_FILTR_CFG_CTS BIT(11) +#define MT_RX_FILTR_CFG_RTS BIT(12) +#define MT_RX_FILTR_CFG_PSPOLL BIT(13) +#define MT_RX_FILTR_CFG_BA BIT(14) +#define MT_RX_FILTR_CFG_BAR BIT(15) +#define MT_RX_FILTR_CFG_CTRL_RSV BIT(16) + +#define MT_AUTO_RSP_CFG 0x1404 + +#define MT_AUTO_RSP_PREAMB_SHORT BIT(4) + +#define MT_LEGACY_BASIC_RATE 0x1408 +#define MT_HT_BASIC_RATE 0x140c + +#define MT_RX_PARSER_CFG 0x1418 +#define MT_RX_PARSER_RX_SET_NAV_ALL BIT(0) + +#define MT_EXT_CCA_CFG 0x141c +#define MT_EXT_CCA_CFG_CCA0 GENMASK(1, 0) +#define MT_EXT_CCA_CFG_CCA1 GENMASK(3, 2) +#define MT_EXT_CCA_CFG_CCA2 GENMASK(5, 4) +#define MT_EXT_CCA_CFG_CCA3 GENMASK(7, 6) +#define MT_EXT_CCA_CFG_CCA_MASK GENMASK(11, 8) +#define MT_EXT_CCA_CFG_ED_CCA_MASK GENMASK(15, 12) + +#define MT_TX_SW_CFG3 0x1478 + +#define MT_PN_PAD_MODE 0x150c + +#define MT_TXOP_HLDR_ET 0x1608 + +#define MT_PROT_AUTO_TX_CFG 0x1648 + +#define MT_RX_STA_CNT0 0x1700 +#define MT_RX_STA_CNT1 0x1704 +#define MT_RX_STA_CNT2 0x1708 +#define MT_TX_STA_CNT0 0x170c +#define MT_TX_STA_CNT1 0x1710 +#define MT_TX_STA_CNT2 0x1714 + +/* Vendor driver defines content of the second word of STAT_FIFO as follows: + * MT_TX_STAT_FIFO_RATE GENMASK(26, 16) + * MT_TX_STAT_FIFO_ETXBF BIT(27) + * MT_TX_STAT_FIFO_SND BIT(28) + * MT_TX_STAT_FIFO_ITXBF BIT(29) + * However, tests show that b16-31 have the same layout as TXWI rate_ctl + * with rate set to rate at which frame was acked. + */ +#define MT_TX_STAT_FIFO 0x1718 +#define MT_TX_STAT_FIFO_VALID BIT(0) +#define MT_TX_STAT_FIFO_PID_TYPE GENMASK(4, 1) +#define MT_TX_STAT_FIFO_SUCCESS BIT(5) +#define MT_TX_STAT_FIFO_AGGR BIT(6) +#define MT_TX_STAT_FIFO_ACKREQ BIT(7) +#define MT_TX_STAT_FIFO_WCID GENMASK(15, 8) +#define MT_TX_STAT_FIFO_RATE GENMASK(31, 16) + +#define MT_TX_AGG_STAT 0x171c + +#define MT_TX_AGG_CNT_BASE0 0x1720 + +#define MT_MPDU_DENSITY_CNT 0x1740 + +#define MT_TX_AGG_CNT_BASE1 0x174c + +#define MT_TX_AGG_CNT(_id) ((_id) < 8 ? \ + MT_TX_AGG_CNT_BASE0 + ((_id) << 2) : \ + MT_TX_AGG_CNT_BASE1 + ((_id - 8) << 2)) + +#define MT_TX_STAT_FIFO_EXT 0x1798 +#define MT_TX_STAT_FIFO_EXT_RETRY GENMASK(7, 0) + +#define MT_BBP_CORE_BASE 0x2000 +#define MT_BBP_IBI_BASE 0x2100 +#define MT_BBP_AGC_BASE 0x2300 +#define MT_BBP_TXC_BASE 0x2400 +#define MT_BBP_RXC_BASE 0x2500 +#define MT_BBP_TXO_BASE 0x2600 +#define MT_BBP_TXBE_BASE 0x2700 +#define MT_BBP_RXFE_BASE 0x2800 +#define MT_BBP_RXO_BASE 0x2900 +#define MT_BBP_DFS_BASE 0x2a00 +#define MT_BBP_TR_BASE 0x2b00 +#define MT_BBP_CAL_BASE 0x2c00 +#define MT_BBP_DSC_BASE 0x2e00 +#define MT_BBP_PFMU_BASE 0x2f00 + +#define MT_BBP(_type, _n) (MT_BBP_##_type##_BASE + ((_n) << 2)) + +#define MT_BBP_CORE_R1_BW GENMASK(4, 3) + +#define MT_BBP_AGC_R0_CTRL_CHAN GENMASK(9, 8) +#define MT_BBP_AGC_R0_BW GENMASK(14, 12) + +/* AGC, R4/R5 */ +#define MT_BBP_AGC_LNA_GAIN GENMASK(21, 16) + +/* AGC, R8/R9 */ +#define MT_BBP_AGC_GAIN GENMASK(14, 8) + +#define MT_BBP_AGC20_RSSI0 GENMASK(7, 0) +#define MT_BBP_AGC20_RSSI1 GENMASK(15, 8) + +#define MT_BBP_TXBE_R0_CTRL_CHAN GENMASK(1, 0) + +#define MT_WCID_ADDR_BASE 0x1800 +#define MT_WCID_ADDR(_n) (MT_WCID_ADDR_BASE + (_n) * 8) + +#define MT_SRAM_BASE 0x4000 + +#define MT_WCID_KEY_BASE 0x8000 +#define MT_WCID_KEY(_n) (MT_WCID_KEY_BASE + (_n) * 32) + +#define MT_WCID_IV_BASE 0xa000 +#define MT_WCID_IV(_n) (MT_WCID_IV_BASE + (_n) * 8) + +#define MT_WCID_ATTR_BASE 0xa800 +#define MT_WCID_ATTR(_n) (MT_WCID_ATTR_BASE + (_n) * 4) + +#define MT_WCID_ATTR_PAIRWISE BIT(0) +#define MT_WCID_ATTR_PKEY_MODE GENMASK(3, 1) +#define MT_WCID_ATTR_BSS_IDX GENMASK(6, 4) +#define MT_WCID_ATTR_RXWI_UDF GENMASK(9, 7) +#define MT_WCID_ATTR_PKEY_MODE_EXT BIT(10) +#define MT_WCID_ATTR_BSS_IDX_EXT BIT(11) +#define MT_WCID_ATTR_WAPI_MCBC BIT(15) +#define MT_WCID_ATTR_WAPI_KEYID GENMASK(31, 24) + +#define MT_SKEY_BASE_0 0xac00 +#define MT_SKEY_BASE_1 0xb400 +#define MT_SKEY_0(_bss, _idx) \ + (MT_SKEY_BASE_0 + (4 * (_bss) + _idx) * 32) +#define MT_SKEY_1(_bss, _idx) \ + (MT_SKEY_BASE_1 + (4 * ((_bss) & 7) + _idx) * 32) +#define MT_SKEY(_bss, _idx) \ + ((_bss & 8) ? MT_SKEY_1(_bss, _idx) : MT_SKEY_0(_bss, _idx)) + +#define MT_SKEY_MODE_BASE_0 0xb000 +#define MT_SKEY_MODE_BASE_1 0xb3f0 +#define MT_SKEY_MODE_0(_bss) \ + (MT_SKEY_MODE_BASE_0 + ((_bss / 2) << 2)) +#define MT_SKEY_MODE_1(_bss) \ + (MT_SKEY_MODE_BASE_1 + ((((_bss) & 7) / 2) << 2)) +#define MT_SKEY_MODE(_bss) \ + ((_bss & 8) ? MT_SKEY_MODE_1(_bss) : MT_SKEY_MODE_0(_bss)) +#define MT_SKEY_MODE_MASK GENMASK(3, 0) +#define MT_SKEY_MODE_SHIFT(_bss, _idx) (4 * ((_idx) + 4 * (_bss & 1))) + +#define MT_BEACON_BASE 0xc000 + +#define MT_TEMP_SENSOR 0x1d000 +#define MT_TEMP_SENSOR_VAL GENMASK(6, 0) + +enum mt76_cipher_type { + MT_CIPHER_NONE, + MT_CIPHER_WEP40, + MT_CIPHER_WEP104, + MT_CIPHER_TKIP, + MT_CIPHER_AES_CCMP, + MT_CIPHER_CKIP40, + MT_CIPHER_CKIP104, + MT_CIPHER_CKIP128, + MT_CIPHER_WAPI, +}; + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/trace.c b/drivers/net/wireless/mediatek/mt7601u/trace.c new file mode 100644 index 0000000000000..8abdd3cd546d3 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/trace.c @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 + +#ifndef __CHECKER__ +#define CREATE_TRACE_POINTS +#include "trace.h" + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/trace.h b/drivers/net/wireless/mediatek/mt7601u/trace.h new file mode 100644 index 0000000000000..289897300ef04 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/trace.h @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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. + */ + +#if !defined(__MT7601U_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __MT7601U_TRACE_H + +#include +#include "mt7601u.h" +#include "mac.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM mt7601u + +#define MAXNAME 32 +#define DEV_ENTRY __array(char, wiphy_name, 32) +#define DEV_ASSIGN strlcpy(__entry->wiphy_name, \ + wiphy_name(dev->hw->wiphy), MAXNAME) +#define DEV_PR_FMT "%s " +#define DEV_PR_ARG __entry->wiphy_name + +#define REG_ENTRY __field(u32, reg) __field(u32, val) +#define REG_ASSIGN __entry->reg = reg; __entry->val = val +#define REG_PR_FMT "%04x=%08x" +#define REG_PR_ARG __entry->reg, __entry->val + +DECLARE_EVENT_CLASS(dev_reg_evt, + TP_PROTO(struct mt7601u_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val), + TP_STRUCT__entry( + DEV_ENTRY + REG_ENTRY + ), + TP_fast_assign( + DEV_ASSIGN; + REG_ASSIGN; + ), + TP_printk( + DEV_PR_FMT REG_PR_FMT, + DEV_PR_ARG, REG_PR_ARG + ) +); + +DEFINE_EVENT(dev_reg_evt, reg_read, + TP_PROTO(struct mt7601u_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +DEFINE_EVENT(dev_reg_evt, reg_write, + TP_PROTO(struct mt7601u_dev *dev, u32 reg, u32 val), + TP_ARGS(dev, reg, val) +); + +TRACE_EVENT(mt_submit_urb, + TP_PROTO(struct mt7601u_dev *dev, struct urb *u), + TP_ARGS(dev, u), + TP_STRUCT__entry( + DEV_ENTRY __field(unsigned, pipe) __field(u32, len) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->pipe = u->pipe; + __entry->len = u->transfer_buffer_length; + ), + TP_printk(DEV_PR_FMT "p:%08x len:%u", + DEV_PR_ARG, __entry->pipe, __entry->len) +); + +#define trace_mt_submit_urb_sync(__dev, __pipe, __len) ({ \ + struct urb u; \ + u.pipe = __pipe; \ + u.transfer_buffer_length = __len; \ + trace_mt_submit_urb(__dev, &u); \ +}) + +TRACE_EVENT(mt_mcu_msg_send, + TP_PROTO(struct mt7601u_dev *dev, + struct sk_buff *skb, u32 csum, bool resp), + TP_ARGS(dev, skb, csum, resp), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, info) + __field(u32, csum) + __field(bool, resp) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->info = *(u32 *)skb->data; + __entry->csum = csum; + __entry->resp = resp; + ), + TP_printk(DEV_PR_FMT "i:%08x c:%08x r:%d", + DEV_PR_ARG, __entry->info, __entry->csum, __entry->resp) +); + +TRACE_EVENT(mt_vend_req, + TP_PROTO(struct mt7601u_dev *dev, unsigned pipe, u8 req, u8 req_type, + u16 val, u16 offset, void *buf, size_t buflen, int ret), + TP_ARGS(dev, pipe, req, req_type, val, offset, buf, buflen, ret), + TP_STRUCT__entry( + DEV_ENTRY + __field(unsigned, pipe) __field(u8, req) __field(u8, req_type) + __field(u16, val) __field(u16, offset) __field(void*, buf) + __field(int, buflen) __field(int, ret) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->pipe = pipe; + __entry->req = req; + __entry->req_type = req_type; + __entry->val = val; + __entry->offset = offset; + __entry->buf = buf; + __entry->buflen = buflen; + __entry->ret = ret; + ), + TP_printk(DEV_PR_FMT + "%d p:%08x req:%02hhx %02hhx val:%04hx %04hx buf:%d %d", + DEV_PR_ARG, __entry->ret, __entry->pipe, __entry->req, + __entry->req_type, __entry->val, __entry->offset, + !!__entry->buf, __entry->buflen) +); + +TRACE_EVENT(ee_read, + TP_PROTO(struct mt7601u_dev *dev, int offset, u16 val), + TP_ARGS(dev, offset, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(int, o) __field(u16, v) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->o = offset; + __entry->v = val; + ), + TP_printk(DEV_PR_FMT "%04x=%04x", DEV_PR_ARG, __entry->o, __entry->v) +); + +DECLARE_EVENT_CLASS(dev_rf_reg_evt, + TP_PROTO(struct mt7601u_dev *dev, u8 bank, u8 reg, u8 val), + TP_ARGS(dev, bank, reg, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, bank) + __field(u8, reg) + __field(u8, val) + ), + TP_fast_assign( + DEV_ASSIGN; + REG_ASSIGN; + __entry->bank = bank; + ), + TP_printk( + DEV_PR_FMT "%02hhx:%02hhx=%02hhx", + DEV_PR_ARG, __entry->bank, __entry->reg, __entry->val + ) +); + +DEFINE_EVENT(dev_rf_reg_evt, rf_read, + TP_PROTO(struct mt7601u_dev *dev, u8 bank, u8 reg, u8 val), + TP_ARGS(dev, bank, reg, val) +); + +DEFINE_EVENT(dev_rf_reg_evt, rf_write, + TP_PROTO(struct mt7601u_dev *dev, u8 bank, u8 reg, u8 val), + TP_ARGS(dev, bank, reg, val) +); + +DECLARE_EVENT_CLASS(dev_bbp_reg_evt, + TP_PROTO(struct mt7601u_dev *dev, u8 reg, u8 val), + TP_ARGS(dev, reg, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, reg) + __field(u8, val) + ), + TP_fast_assign( + DEV_ASSIGN; + REG_ASSIGN; + ), + TP_printk( + DEV_PR_FMT "%02hhx=%02hhx", + DEV_PR_ARG, __entry->reg, __entry->val + ) +); + +DEFINE_EVENT(dev_bbp_reg_evt, bbp_read, + TP_PROTO(struct mt7601u_dev *dev, u8 reg, u8 val), + TP_ARGS(dev, reg, val) +); + +DEFINE_EVENT(dev_bbp_reg_evt, bbp_write, + TP_PROTO(struct mt7601u_dev *dev, u8 reg, u8 val), + TP_ARGS(dev, reg, val) +); + +DECLARE_EVENT_CLASS(dev_simple_evt, + TP_PROTO(struct mt7601u_dev *dev, u8 val), + TP_ARGS(dev, val), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, val) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->val = val; + ), + TP_printk( + DEV_PR_FMT "%02hhx", DEV_PR_ARG, __entry->val + ) +); + +DEFINE_EVENT(dev_simple_evt, temp_mode, + TP_PROTO(struct mt7601u_dev *dev, u8 val), + TP_ARGS(dev, val) +); + +DEFINE_EVENT(dev_simple_evt, read_temp, + TP_PROTO(struct mt7601u_dev *dev, u8 val), + TP_ARGS(dev, val) +); + +DEFINE_EVENT(dev_simple_evt, freq_cal_adjust, + TP_PROTO(struct mt7601u_dev *dev, u8 val), + TP_ARGS(dev, val) +); + +TRACE_EVENT(freq_cal_offset, + TP_PROTO(struct mt7601u_dev *dev, u8 phy_mode, s8 freq_off), + TP_ARGS(dev, phy_mode, freq_off), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, phy_mode) + __field(s8, freq_off) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->phy_mode = phy_mode; + __entry->freq_off = freq_off; + ), + TP_printk(DEV_PR_FMT "phy:%02hhx off:%02hhx", + DEV_PR_ARG, __entry->phy_mode, __entry->freq_off) +); + +TRACE_EVENT(mt_rx, + TP_PROTO(struct mt7601u_dev *dev, struct mt7601u_rxwi *rxwi, u32 f), + TP_ARGS(dev, rxwi, f), + TP_STRUCT__entry( + DEV_ENTRY + __field_struct(struct mt7601u_rxwi, rxwi) + __field(u32, fce_info) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->rxwi = *rxwi; + __entry->fce_info = f; + ), + TP_printk(DEV_PR_FMT "rxi:%08x ctl:%08x frag_sn:%04hx rate:%04hx " + "uknw:%02hhx z:%02hhx%02hhx%02hhx snr:%02hhx " + "ant:%02hhx gain:%02hhx freq_o:%02hhx " + "r:%08x ea:%08x fce:%08x", DEV_PR_ARG, + le32_to_cpu(__entry->rxwi.rxinfo), + le32_to_cpu(__entry->rxwi.ctl), + le16_to_cpu(__entry->rxwi.frag_sn), + le16_to_cpu(__entry->rxwi.rate), + __entry->rxwi.unknown, + __entry->rxwi.zero[0], __entry->rxwi.zero[1], + __entry->rxwi.zero[2], + __entry->rxwi.snr, __entry->rxwi.ant, + __entry->rxwi.gain, __entry->rxwi.freq_off, + __entry->rxwi.resv2, __entry->rxwi.expert_ant, + __entry->fce_info) +); + +TRACE_EVENT(mt_tx, + TP_PROTO(struct mt7601u_dev *dev, struct sk_buff *skb, + struct mt76_sta *sta, struct mt76_txwi *h), + TP_ARGS(dev, skb, sta, h), + TP_STRUCT__entry( + DEV_ENTRY + __field_struct(struct mt76_txwi, h) + __field(struct sk_buff *, skb) + __field(struct mt76_sta *, sta) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->h = *h; + __entry->skb = skb; + __entry->sta = sta; + ), + TP_printk(DEV_PR_FMT "skb:%p sta:%p flg:%04hx rate_ctl:%04hx " + "ack:%02hhx wcid:%02hhx len_ctl:%05hx", DEV_PR_ARG, + __entry->skb, __entry->sta, + le16_to_cpu(__entry->h.flags), + le16_to_cpu(__entry->h.rate_ctl), + __entry->h.ack_ctl, __entry->h.wcid, + le16_to_cpu(__entry->h.len_ctl)) +); + +TRACE_EVENT(mt_tx_dma_done, + TP_PROTO(struct mt7601u_dev *dev, struct sk_buff *skb), + TP_ARGS(dev, skb), + TP_STRUCT__entry( + DEV_ENTRY + __field(struct sk_buff *, skb) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->skb = skb; + ), + TP_printk(DEV_PR_FMT "%p", DEV_PR_ARG, __entry->skb) +); + +TRACE_EVENT(mt_tx_status_cleaned, + TP_PROTO(struct mt7601u_dev *dev, int cleaned), + TP_ARGS(dev, cleaned), + TP_STRUCT__entry( + DEV_ENTRY + __field(int, cleaned) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->cleaned = cleaned; + ), + TP_printk(DEV_PR_FMT "%d", DEV_PR_ARG, __entry->cleaned) +); + +TRACE_EVENT(mt_tx_status, + TP_PROTO(struct mt7601u_dev *dev, u32 stat1, u32 stat2), + TP_ARGS(dev, stat1, stat2), + TP_STRUCT__entry( + DEV_ENTRY + __field(u32, stat1) __field(u32, stat2) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->stat1 = stat1; + __entry->stat2 = stat2; + ), + TP_printk(DEV_PR_FMT "%08x %08x", + DEV_PR_ARG, __entry->stat1, __entry->stat2) +); + +TRACE_EVENT(mt_rx_dma_aggr, + TP_PROTO(struct mt7601u_dev *dev, int cnt, bool paged), + TP_ARGS(dev, cnt, paged), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, cnt) + __field(bool, paged) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->cnt = cnt; + __entry->paged = paged; + ), + TP_printk(DEV_PR_FMT "cnt:%d paged:%d", + DEV_PR_ARG, __entry->cnt, __entry->paged) +); + +DEFINE_EVENT(dev_simple_evt, set_key, + TP_PROTO(struct mt7601u_dev *dev, u8 val), + TP_ARGS(dev, val) +); + +TRACE_EVENT(set_shared_key, + TP_PROTO(struct mt7601u_dev *dev, u8 vid, u8 key), + TP_ARGS(dev, vid, key), + TP_STRUCT__entry( + DEV_ENTRY + __field(u8, vid) + __field(u8, key) + ), + TP_fast_assign( + DEV_ASSIGN; + __entry->vid = vid; + __entry->key = key; + ), + TP_printk(DEV_PR_FMT "phy:%02hhx off:%02hhx", + DEV_PR_ARG, __entry->vid, __entry->key) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/net/wireless/mediatek/mt7601u/tx.c b/drivers/net/wireless/mediatek/mt7601u/tx.c new file mode 100644 index 0000000000000..0be2080ceab38 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/tx.c @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "trace.h" + +enum mt76_txq_id { + MT_TXQ_VO = IEEE80211_AC_VO, + MT_TXQ_VI = IEEE80211_AC_VI, + MT_TXQ_BE = IEEE80211_AC_BE, + MT_TXQ_BK = IEEE80211_AC_BK, + MT_TXQ_PSD, + MT_TXQ_MCU, + __MT_TXQ_MAX +}; + +/* Hardware uses mirrored order of queues with Q0 having the highest priority */ +static u8 q2hwq(u8 q) +{ + return q ^ 0x3; +} + +/* Take mac80211 Q id from the skb and translate it to hardware Q id */ +static u8 skb2q(struct sk_buff *skb) +{ + int qid = skb_get_queue_mapping(skb); + + if (WARN_ON(qid >= MT_TXQ_PSD)) { + qid = MT_TXQ_BE; + skb_set_queue_mapping(skb, qid); + } + + return q2hwq(qid); +} + +/* Note: TX retry reporting is a bit broken. + * Retries are reported only once per AMPDU and often come a frame early + * i.e. they are reported in the last status preceding the AMPDU. Apart + * from the fact that it's hard to know the length of the AMPDU (which is + * required to know to how many consecutive frames retries should be + * applied), if status comes early on full FIFO it gets lost and retries + * of the whole AMPDU become invisible. + * As a work-around encode the desired rate in PKT_ID of TX descriptor + * and based on that guess the retries (every rate is tried once). + * Only downside here is that for MCS0 we have to rely solely on + * transmission failures as no retries can ever be reported. + * Not having to read EXT_FIFO has a nice effect of doubling the number + * of reports which can be fetched. + * Also the vendor driver never uses the EXT_FIFO register so it may be + * undertested. + */ +static u8 mt7601u_tx_pktid_enc(struct mt7601u_dev *dev, u8 rate, bool is_probe) +{ + u8 encoded = (rate + 1) + is_probe * 8; + + /* Because PKT_ID 0 disables status reporting only 15 values are + * available but 16 are needed (8 MCS * 2 for encoding is_probe) + * - we need to cram together two rates. MCS0 and MCS7 with is_probe + * share PKT_ID 9. + */ + if (is_probe && rate == 7) + return encoded - 7; + + return encoded; +} + +static void +mt7601u_tx_pktid_dec(struct mt7601u_dev *dev, struct mt76_tx_status *stat) +{ + u8 req_rate = stat->pktid; + u8 eff_rate = stat->rate & 0x7; + + req_rate -= 1; + + if (req_rate > 7) { + stat->is_probe = true; + req_rate -= 8; + + /* Decide between MCS0 and MCS7 which share pktid 9 */ + if (!req_rate && eff_rate) + req_rate = 7; + } + + stat->retry = req_rate - eff_rate; +} + +static void mt7601u_tx_skb_remove_dma_overhead(struct sk_buff *skb, + struct ieee80211_tx_info *info) +{ + int pkt_len = (unsigned long)info->status.status_driver_data[0]; + + skb_pull(skb, sizeof(struct mt76_txwi) + 4); + if (ieee80211_get_hdrlen_from_skb(skb) % 4) + mt76_remove_hdr_pad(skb); + + skb_trim(skb, pkt_len); +} + +void mt7601u_tx_status(struct mt7601u_dev *dev, struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + mt7601u_tx_skb_remove_dma_overhead(skb, info); + + ieee80211_tx_info_clear_status(info); + info->status.rates[0].idx = -1; + info->flags |= IEEE80211_TX_STAT_ACK; + ieee80211_tx_status(dev->hw, skb); +} + +static int mt7601u_skb_rooms(struct mt7601u_dev *dev, struct sk_buff *skb) +{ + int hdr_len = ieee80211_get_hdrlen_from_skb(skb); + u32 need_head; + + need_head = sizeof(struct mt76_txwi) + 4; + if (hdr_len % 4) + need_head += 2; + + return skb_cow(skb, need_head); +} + +static struct mt76_txwi * +mt7601u_push_txwi(struct mt7601u_dev *dev, struct sk_buff *skb, + struct ieee80211_sta *sta, struct mt76_wcid *wcid, + int pkt_len) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_tx_rate *rate = &info->control.rates[0]; + struct mt76_txwi *txwi; + unsigned long flags; + bool is_probe; + u32 pkt_id; + u16 rate_ctl; + u8 nss; + + txwi = (struct mt76_txwi *)skb_push(skb, sizeof(struct mt76_txwi)); + memset(txwi, 0, sizeof(*txwi)); + + if (!wcid->tx_rate_set) + ieee80211_get_tx_rates(info->control.vif, sta, skb, + info->control.rates, 1); + + spin_lock_irqsave(&dev->lock, flags); + if (rate->idx < 0 || !rate->count) + rate_ctl = wcid->tx_rate; + else + rate_ctl = mt76_mac_tx_rate_val(dev, rate, &nss); + spin_unlock_irqrestore(&dev->lock, flags); + txwi->rate_ctl = cpu_to_le16(rate_ctl); + + if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) + txwi->ack_ctl |= MT_TXWI_ACK_CTL_REQ; + if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) + txwi->ack_ctl |= MT_TXWI_ACK_CTL_NSEQ; + + if ((info->flags & IEEE80211_TX_CTL_AMPDU) && sta) { + u8 ba_size = IEEE80211_MIN_AMPDU_BUF; + + ba_size <<= sta->ht_cap.ampdu_factor; + ba_size = min_t(int, 63, ba_size); + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + ba_size = 0; + txwi->ack_ctl |= MT76_SET(MT_TXWI_ACK_CTL_BA_WINDOW, ba_size); + + txwi->flags = cpu_to_le16(MT_TXWI_FLAGS_AMPDU | + MT76_SET(MT_TXWI_FLAGS_MPDU_DENSITY, + sta->ht_cap.ampdu_density)); + if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE) + txwi->flags = 0; + } + + txwi->wcid = wcid->idx; + + is_probe = !!(info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE); + pkt_id = mt7601u_tx_pktid_enc(dev, rate_ctl & 0x7, is_probe); + pkt_len |= MT76_SET(MT_TXWI_LEN_PKTID, pkt_id); + txwi->len_ctl = cpu_to_le16(pkt_len); + + return txwi; +} + +void mt7601u_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, + struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct mt7601u_dev *dev = hw->priv; + struct ieee80211_vif *vif = info->control.vif; + struct ieee80211_sta *sta = control->sta; + struct mt76_sta *msta = NULL; + struct mt76_wcid *wcid = dev->mon_wcid; + struct mt76_txwi *txwi; + int pkt_len = skb->len; + int hw_q = skb2q(skb); + + BUILD_BUG_ON(ARRAY_SIZE(info->status.status_driver_data) < 1); + info->status.status_driver_data[0] = (void *)(unsigned long)pkt_len; + + if (mt7601u_skb_rooms(dev, skb) || mt76_insert_hdr_pad(skb)) { + ieee80211_free_txskb(dev->hw, skb); + return; + } + + if (sta) { + msta = (struct mt76_sta *) sta->drv_priv; + wcid = &msta->wcid; + } else if (vif) { + struct mt76_vif *mvif = (struct mt76_vif *)vif->drv_priv; + + wcid = &mvif->group_wcid; + } + + txwi = mt7601u_push_txwi(dev, skb, sta, wcid, pkt_len); + + if (mt7601u_dma_enqueue_tx(dev, skb, wcid, hw_q)) + return; + + trace_mt_tx(dev, skb, msta, txwi); +} + +void mt7601u_tx_stat(struct work_struct *work) +{ + struct mt7601u_dev *dev = container_of(work, struct mt7601u_dev, + stat_work.work); + struct mt76_tx_status stat; + unsigned long flags; + int cleaned = 0; + + while (!test_bit(MT7601U_STATE_REMOVED, &dev->state)) { + stat = mt7601u_mac_fetch_tx_status(dev); + if (!stat.valid) + break; + + mt7601u_tx_pktid_dec(dev, &stat); + mt76_send_tx_status(dev, &stat); + + cleaned++; + } + trace_mt_tx_status_cleaned(dev, cleaned); + + spin_lock_irqsave(&dev->tx_lock, flags); + if (cleaned) + queue_delayed_work(dev->stat_wq, &dev->stat_work, + msecs_to_jiffies(10)); + else if (test_and_clear_bit(MT7601U_STATE_MORE_STATS, &dev->state)) + queue_delayed_work(dev->stat_wq, &dev->stat_work, + msecs_to_jiffies(20)); + else + clear_bit(MT7601U_STATE_READING_STATS, &dev->state); + spin_unlock_irqrestore(&dev->tx_lock, flags); +} + +int mt7601u_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + u16 queue, const struct ieee80211_tx_queue_params *params) +{ + struct mt7601u_dev *dev = hw->priv; + u8 cw_min = 5, cw_max = 10, hw_q = q2hwq(queue); + u32 val; + + /* TODO: should we do funny things with the parameters? + * See what mt7601u_set_default_edca() used to do in init.c. + */ + + if (params->cw_min) + cw_min = fls(params->cw_min); + if (params->cw_max) + cw_max = fls(params->cw_max); + + WARN_ON(params->txop > 0xff); + WARN_ON(params->aifs > 0xf); + WARN_ON(cw_min > 0xf); + WARN_ON(cw_max > 0xf); + + val = MT76_SET(MT_EDCA_CFG_AIFSN, params->aifs) | + MT76_SET(MT_EDCA_CFG_CWMIN, cw_min) | + MT76_SET(MT_EDCA_CFG_CWMAX, cw_max); + /* TODO: based on user-controlled EnableTxBurst var vendor drv sets + * a really long txop on AC0 (see connect.c:2009) but only on + * connect? When not connected should be 0. + */ + if (!hw_q) + val |= 0x60; + else + val |= MT76_SET(MT_EDCA_CFG_TXOP, params->txop); + mt76_wr(dev, MT_EDCA_CFG_AC(hw_q), val); + + val = mt76_rr(dev, MT_WMM_TXOP(hw_q)); + val &= ~(MT_WMM_TXOP_MASK << MT_WMM_TXOP_SHIFT(hw_q)); + val |= params->txop << MT_WMM_TXOP_SHIFT(hw_q); + mt76_wr(dev, MT_WMM_TXOP(hw_q), val); + + val = mt76_rr(dev, MT_WMM_AIFSN); + val &= ~(MT_WMM_AIFSN_MASK << MT_WMM_AIFSN_SHIFT(hw_q)); + val |= params->aifs << MT_WMM_AIFSN_SHIFT(hw_q); + mt76_wr(dev, MT_WMM_AIFSN, val); + + val = mt76_rr(dev, MT_WMM_CWMIN); + val &= ~(MT_WMM_CWMIN_MASK << MT_WMM_CWMIN_SHIFT(hw_q)); + val |= cw_min << MT_WMM_CWMIN_SHIFT(hw_q); + mt76_wr(dev, MT_WMM_CWMIN, val); + + val = mt76_rr(dev, MT_WMM_CWMAX); + val &= ~(MT_WMM_CWMAX_MASK << MT_WMM_CWMAX_SHIFT(hw_q)); + val |= cw_max << MT_WMM_CWMAX_SHIFT(hw_q); + mt76_wr(dev, MT_WMM_CWMAX, val); + + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/usb.c b/drivers/net/wireless/mediatek/mt7601u/usb.c new file mode 100644 index 0000000000000..54dba40018651 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/usb.c @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2015 Jakub Kicinski + * + * 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 "mt7601u.h" +#include "usb.h" +#include "trace.h" + +static struct usb_device_id mt7601u_device_table[] = { + { USB_DEVICE(0x0b05, 0x17d3) }, + { USB_DEVICE(0x0e8d, 0x760a) }, + { USB_DEVICE(0x0e8d, 0x760b) }, + { USB_DEVICE(0x13d3, 0x3431) }, + { USB_DEVICE(0x13d3, 0x3434) }, + { USB_DEVICE(0x148f, 0x7601) }, + { USB_DEVICE(0x148f, 0x760a) }, + { USB_DEVICE(0x148f, 0x760b) }, + { USB_DEVICE(0x148f, 0x760c) }, + { USB_DEVICE(0x148f, 0x760d) }, + { USB_DEVICE(0x2001, 0x3d04) }, + { USB_DEVICE(0x2717, 0x4106) }, + { USB_DEVICE(0x2955, 0x0001) }, + { USB_DEVICE(0x2955, 0x1001) }, + { USB_DEVICE(0x2a5f, 0x1000) }, + { USB_DEVICE(0x7392, 0x7710) }, + { 0, } +}; + +bool mt7601u_usb_alloc_buf(struct mt7601u_dev *dev, size_t len, + struct mt7601u_dma_buf *buf) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + + buf->len = len; + buf->urb = usb_alloc_urb(0, GFP_KERNEL); + buf->buf = usb_alloc_coherent(usb_dev, buf->len, GFP_KERNEL, &buf->dma); + + return !buf->urb || !buf->buf; +} + +void mt7601u_usb_free_buf(struct mt7601u_dev *dev, struct mt7601u_dma_buf *buf) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + + usb_free_coherent(usb_dev, buf->len, buf->buf, buf->dma); + usb_free_urb(buf->urb); +} + +int mt7601u_usb_submit_buf(struct mt7601u_dev *dev, int dir, int ep_idx, + struct mt7601u_dma_buf *buf, gfp_t gfp, + usb_complete_t complete_fn, void *context) +{ + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + unsigned pipe; + int ret; + + if (dir == USB_DIR_IN) + pipe = usb_rcvbulkpipe(usb_dev, dev->in_eps[ep_idx]); + else + pipe = usb_sndbulkpipe(usb_dev, dev->out_eps[ep_idx]); + + usb_fill_bulk_urb(buf->urb, usb_dev, pipe, buf->buf, buf->len, + complete_fn, context); + buf->urb->transfer_dma = buf->dma; + buf->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + trace_mt_submit_urb(dev, buf->urb); + ret = usb_submit_urb(buf->urb, gfp); + if (ret) + dev_err(dev->dev, "Error: submit URB dir:%d ep:%d failed:%d\n", + dir, ep_idx, ret); + return ret; +} + +void mt7601u_complete_urb(struct urb *urb) +{ + struct completion *cmpl = urb->context; + + complete(cmpl); +} + +static int +__mt7601u_vendor_request(struct mt7601u_dev *dev, const u8 req, + const u8 direction, const u16 val, const u16 offset, + void *buf, const size_t buflen) +{ + int i, ret; + struct usb_device *usb_dev = mt7601u_to_usb_dev(dev); + const u8 req_type = direction | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + const unsigned int pipe = (direction == USB_DIR_IN) ? + usb_rcvctrlpipe(usb_dev, 0) : usb_sndctrlpipe(usb_dev, 0); + + for (i = 0; i < MT_VEND_REQ_MAX_RETRY; i++) { + ret = usb_control_msg(usb_dev, pipe, req, req_type, + val, offset, buf, buflen, + MT_VEND_REQ_TOUT_MS); + trace_mt_vend_req(dev, pipe, req, req_type, val, offset, + buf, buflen, ret); + + if (ret >= 0 || ret == -ENODEV) + return ret; + + msleep(5); + } + + dev_err(dev->dev, "Vendor request req:%02x off:%04x failed:%d\n", + req, offset, ret); + + return ret; +} + +int +mt7601u_vendor_request(struct mt7601u_dev *dev, const u8 req, + const u8 direction, const u16 val, const u16 offset, + void *buf, const size_t buflen) +{ + int ret; + + mutex_lock(&dev->vendor_req_mutex); + + ret = __mt7601u_vendor_request(dev, req, direction, val, offset, + buf, buflen); + if (ret == -ENODEV) + set_bit(MT7601U_STATE_REMOVED, &dev->state); + + mutex_unlock(&dev->vendor_req_mutex); + + return ret; +} + +void mt7601u_vendor_reset(struct mt7601u_dev *dev) +{ + mt7601u_vendor_request(dev, MT_VEND_DEV_MODE, USB_DIR_OUT, + MT_VEND_DEV_MODE_RESET, 0, NULL, 0); +} + +u32 mt7601u_rr(struct mt7601u_dev *dev, u32 offset) +{ + int ret; + __le32 reg; + u32 val; + + WARN_ONCE(offset > USHRT_MAX, "read high off:%08x", offset); + + ret = mt7601u_vendor_request(dev, MT_VEND_MULTI_READ, USB_DIR_IN, + 0, offset, ®, sizeof(reg)); + val = le32_to_cpu(reg); + if (ret > 0 && ret != sizeof(reg)) { + dev_err(dev->dev, "Error: wrong size read:%d off:%08x\n", + ret, offset); + val = ~0; + } + + trace_reg_read(dev, offset, val); + return val; +} + +int mt7601u_vendor_single_wr(struct mt7601u_dev *dev, const u8 req, + const u16 offset, const u32 val) +{ + int ret; + + ret = mt7601u_vendor_request(dev, req, USB_DIR_OUT, + val & 0xffff, offset, NULL, 0); + if (ret) + return ret; + return mt7601u_vendor_request(dev, req, USB_DIR_OUT, + val >> 16, offset + 2, NULL, 0); +} + +void mt7601u_wr(struct mt7601u_dev *dev, u32 offset, u32 val) +{ + WARN_ONCE(offset > USHRT_MAX, "write high off:%08x", offset); + + mt7601u_vendor_single_wr(dev, MT_VEND_WRITE, offset, val); + trace_reg_write(dev, offset, val); +} + +u32 mt7601u_rmw(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val) +{ + val |= mt7601u_rr(dev, offset) & ~mask; + mt7601u_wr(dev, offset, val); + return val; +} + +u32 mt7601u_rmc(struct mt7601u_dev *dev, u32 offset, u32 mask, u32 val) +{ + u32 reg = mt7601u_rr(dev, offset); + + val |= reg & ~mask; + if (reg != val) + mt7601u_wr(dev, offset, val); + return val; +} + +void mt7601u_wr_copy(struct mt7601u_dev *dev, u32 offset, + const void *data, int len) +{ + WARN_ONCE(offset & 3, "unaligned write copy off:%08x", offset); + WARN_ONCE(len & 3, "short write copy off:%08x", offset); + + mt7601u_burst_write_regs(dev, offset, data, len / 4); +} + +void mt7601u_addr_wr(struct mt7601u_dev *dev, const u32 offset, const u8 *addr) +{ + mt7601u_wr(dev, offset, get_unaligned_le32(addr)); + mt7601u_wr(dev, offset + 4, addr[4] | addr[5] << 8); +} + +static int mt7601u_assign_pipes(struct usb_interface *usb_intf, + struct mt7601u_dev *dev) +{ + struct usb_endpoint_descriptor *ep_desc; + struct usb_host_interface *intf_desc = usb_intf->cur_altsetting; + unsigned i, ep_i = 0, ep_o = 0; + + BUILD_BUG_ON(sizeof(dev->in_eps) < __MT_EP_IN_MAX); + BUILD_BUG_ON(sizeof(dev->out_eps) < __MT_EP_OUT_MAX); + + for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { + ep_desc = &intf_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(ep_desc) && + ep_i++ < __MT_EP_IN_MAX) { + dev->in_eps[ep_i - 1] = usb_endpoint_num(ep_desc); + dev->in_max_packet = usb_endpoint_maxp(ep_desc); + /* Note: this is ignored by usb sub-system but vendor + * code does it. We can drop this at some point. + */ + dev->in_eps[ep_i - 1] |= USB_DIR_IN; + } else if (usb_endpoint_is_bulk_out(ep_desc) && + ep_o++ < __MT_EP_OUT_MAX) { + dev->out_eps[ep_o - 1] = usb_endpoint_num(ep_desc); + dev->out_max_packet = usb_endpoint_maxp(ep_desc); + } + } + + if (ep_i != __MT_EP_IN_MAX || ep_o != __MT_EP_OUT_MAX) { + dev_err(dev->dev, "Error: wrong pipe number in:%d out:%d\n", + ep_i, ep_o); + return -EINVAL; + } + + return 0; +} + +static int mt7601u_probe(struct usb_interface *usb_intf, + const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(usb_intf); + struct mt7601u_dev *dev; + u32 asic_rev, mac_rev; + int ret; + + dev = mt7601u_alloc_device(&usb_intf->dev); + if (!dev) + return -ENOMEM; + + usb_dev = usb_get_dev(usb_dev); + usb_reset_device(usb_dev); + + usb_set_intfdata(usb_intf, dev); + + ret = mt7601u_assign_pipes(usb_intf, dev); + if (ret) + goto err; + ret = mt7601u_wait_asic_ready(dev); + if (ret) + goto err; + + asic_rev = mt7601u_rr(dev, MT_ASIC_VERSION); + mac_rev = mt7601u_rr(dev, MT_MAC_CSR0); + dev_info(dev->dev, "ASIC revision: %08x MAC revision: %08x\n", + asic_rev, mac_rev); + + /* Note: vendor driver skips this check for MT7601U */ + if (!(mt7601u_rr(dev, MT_EFUSE_CTRL) & MT_EFUSE_CTRL_SEL)) + dev_warn(dev->dev, "Warning: eFUSE not present\n"); + + ret = mt7601u_init_hardware(dev); + if (ret) + goto err; + ret = mt7601u_register_device(dev); + if (ret) + goto err_hw; + + set_bit(MT7601U_STATE_INITIALIZED, &dev->state); + + return 0; +err_hw: + mt7601u_cleanup(dev); +err: + usb_set_intfdata(usb_intf, NULL); + usb_put_dev(interface_to_usbdev(usb_intf)); + + destroy_workqueue(dev->stat_wq); + ieee80211_free_hw(dev->hw); + return ret; +} + +static void mt7601u_disconnect(struct usb_interface *usb_intf) +{ + struct mt7601u_dev *dev = usb_get_intfdata(usb_intf); + + ieee80211_unregister_hw(dev->hw); + mt7601u_cleanup(dev); + + usb_set_intfdata(usb_intf, NULL); + usb_put_dev(interface_to_usbdev(usb_intf)); + + destroy_workqueue(dev->stat_wq); + ieee80211_free_hw(dev->hw); +} + +static int mt7601u_suspend(struct usb_interface *usb_intf, pm_message_t state) +{ + struct mt7601u_dev *dev = usb_get_intfdata(usb_intf); + + mt7601u_cleanup(dev); + + return 0; +} + +static int mt7601u_resume(struct usb_interface *usb_intf) +{ + struct mt7601u_dev *dev = usb_get_intfdata(usb_intf); + int ret; + + ret = mt7601u_init_hardware(dev); + if (ret) + return ret; + + set_bit(MT7601U_STATE_INITIALIZED, &dev->state); + + return 0; +} + +MODULE_DEVICE_TABLE(usb, mt7601u_device_table); +MODULE_FIRMWARE(MT7601U_FIRMWARE); +MODULE_LICENSE("GPL"); + +static struct usb_driver mt7601u_driver = { + .name = KBUILD_MODNAME, + .id_table = mt7601u_device_table, + .probe = mt7601u_probe, + .disconnect = mt7601u_disconnect, + .suspend = mt7601u_suspend, + .resume = mt7601u_resume, + .reset_resume = mt7601u_resume, + .soft_unbind = 1, + .disable_hub_initiated_lpm = 1, +}; +module_usb_driver(mt7601u_driver); diff --git a/drivers/net/wireless/mediatek/mt7601u/usb.h b/drivers/net/wireless/mediatek/mt7601u/usb.h new file mode 100644 index 0000000000000..49e188fa37983 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/usb.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 Jakub Kicinski + * + * 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 __MT7601U_USB_H +#define __MT7601U_USB_H + +#include "mt7601u.h" + +#define MT7601U_FIRMWARE "mt7601u.bin" + +#define MT_VEND_REQ_MAX_RETRY 10 +#define MT_VEND_REQ_TOUT_MS 300 + +#define MT_VEND_DEV_MODE_RESET 1 + +enum mt_vendor_req { + MT_VEND_DEV_MODE = 1, + MT_VEND_WRITE = 2, + MT_VEND_MULTI_READ = 7, + MT_VEND_WRITE_FCE = 0x42, +}; + +enum mt_usb_ep_in { + MT_EP_IN_PKT_RX, + MT_EP_IN_CMD_RESP, + __MT_EP_IN_MAX, +}; + +enum mt_usb_ep_out { + MT_EP_OUT_INBAND_CMD, + MT_EP_OUT_AC_BK, + MT_EP_OUT_AC_BE, + MT_EP_OUT_AC_VI, + MT_EP_OUT_AC_VO, + MT_EP_OUT_HCCA, + __MT_EP_OUT_MAX, +}; + +static inline struct usb_device *mt7601u_to_usb_dev(struct mt7601u_dev *mt7601u) +{ + return interface_to_usbdev(to_usb_interface(mt7601u->dev)); +} + +static inline bool mt7601u_urb_has_error(struct urb *urb) +{ + return urb->status && + urb->status != -ENOENT && + urb->status != -ECONNRESET && + urb->status != -ESHUTDOWN; +} + +bool mt7601u_usb_alloc_buf(struct mt7601u_dev *dev, size_t len, + struct mt7601u_dma_buf *buf); +void mt7601u_usb_free_buf(struct mt7601u_dev *dev, struct mt7601u_dma_buf *buf); +int mt7601u_usb_submit_buf(struct mt7601u_dev *dev, int dir, int ep_idx, + struct mt7601u_dma_buf *buf, gfp_t gfp, + usb_complete_t complete_fn, void *context); +void mt7601u_complete_urb(struct urb *urb); + +int mt7601u_vendor_request(struct mt7601u_dev *dev, const u8 req, + const u8 direction, const u16 val, const u16 offset, + void *buf, const size_t buflen); +void mt7601u_vendor_reset(struct mt7601u_dev *dev); +int mt7601u_vendor_single_wr(struct mt7601u_dev *dev, const u8 req, + const u16 offset, const u32 val); + +#endif diff --git a/drivers/net/wireless/mediatek/mt7601u/util.c b/drivers/net/wireless/mediatek/mt7601u/util.c new file mode 100644 index 0000000000000..7c1787c1ddcdb --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/util.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * + * 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 "mt7601u.h" + +void mt76_remove_hdr_pad(struct sk_buff *skb) +{ + int len = ieee80211_get_hdrlen_from_skb(skb); + + memmove(skb->data + 2, skb->data, len); + skb_pull(skb, 2); +} + +int mt76_insert_hdr_pad(struct sk_buff *skb) +{ + int len = ieee80211_get_hdrlen_from_skb(skb); + int ret; + + if (len % 4 == 0) + return 0; + + ret = skb_cow(skb, 2); + if (ret) + return ret; + + skb_push(skb, 2); + memmove(skb->data, skb->data + 2, len); + + skb->data[len] = 0; + skb->data[len + 1] = 0; + return 0; +} diff --git a/drivers/net/wireless/mediatek/mt7601u/util.h b/drivers/net/wireless/mediatek/mt7601u/util.h new file mode 100644 index 0000000000000..b89140bf12107 --- /dev/null +++ b/drivers/net/wireless/mediatek/mt7601u/util.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2004 - 2009 Ivo van Doorn + * + * 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 __MT76_UTIL_H +#define __MT76_UTIL_H + +/* + * Power of two check, this will check + * if the mask that has been given contains and contiguous set of bits. + * Note that we cannot use the is_power_of_2() function since this + * check must be done at compile-time. + */ +#define is_power_of_two(x) ( !((x) & ((x)-1)) ) +#define low_bit_mask(x) ( ((x)-1) & ~(x) ) +#define is_valid_mask(x) is_power_of_two(1LU + (x) + low_bit_mask(x)) + +/* + * Macros to find first set bit in a variable. + * These macros behave the same as the __ffs() functions but + * the most important difference that this is done during + * compile-time rather then run-time. + */ +#define compile_ffs2(__x) \ + __builtin_choose_expr(((__x) & 0x1), 0, 1) + +#define compile_ffs4(__x) \ + __builtin_choose_expr(((__x) & 0x3), \ + (compile_ffs2((__x))), \ + (compile_ffs2((__x) >> 2) + 2)) + +#define compile_ffs8(__x) \ + __builtin_choose_expr(((__x) & 0xf), \ + (compile_ffs4((__x))), \ + (compile_ffs4((__x) >> 4) + 4)) + +#define compile_ffs16(__x) \ + __builtin_choose_expr(((__x) & 0xff), \ + (compile_ffs8((__x))), \ + (compile_ffs8((__x) >> 8) + 8)) + +#define compile_ffs32(__x) \ + __builtin_choose_expr(((__x) & 0xffff), \ + (compile_ffs16((__x))), \ + (compile_ffs16((__x) >> 16) + 16)) + +/* + * This macro will check the requirements for the FIELD{8,16,32} macros + * The mask should be a constant non-zero contiguous set of bits which + * does not exceed the given typelimit. + */ +#define FIELD_CHECK(__mask) \ + BUILD_BUG_ON(!(__mask) || !is_valid_mask(__mask)) + +#define MT76_SET(_mask, _val) \ + ({ \ + FIELD_CHECK(_mask); \ + (((u32) (_val)) << compile_ffs32(_mask)) & _mask; \ + }) + +#define MT76_GET(_mask, _val) \ + ({ \ + FIELD_CHECK(_mask); \ + (u32) (((_val) & _mask) >> compile_ffs32(_mask)); \ + }) + +#endif diff --git a/drivers/net/wireless/ti/wl18xx/debugfs.c b/drivers/net/wireless/ti/wl18xx/debugfs.c index 911ae9a256aae..432192d3228e9 100644 --- a/drivers/net/wireless/ti/wl18xx/debugfs.c +++ b/drivers/net/wireless/ti/wl18xx/debugfs.c @@ -296,6 +296,127 @@ static const struct file_operations radar_detection_ops = { .llseek = default_llseek, }; +static ssize_t radar_debug_mode_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in radar_debug_mode!"); + return -EINVAL; + } + + /* valid values: 0/1 */ + if (!(value == 0 || value == 1)) { + wl1271_warning("value is not in valid!"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + wl->radar_debug_mode = value; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif_ap(wl, wlvif) { + wlcore_cmd_generic_cfg(wl, wlvif, + WLCORE_CFG_FEATURE_RADAR_DEBUG, + wl->radar_debug_mode, 0); + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static ssize_t radar_debug_mode_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + return wl1271_format_buffer(userbuf, count, ppos, + "%d\n", wl->radar_debug_mode); +} + +static const struct file_operations radar_debug_mode_ops = { + .write = radar_debug_mode_write, + .read = radar_debug_mode_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t diversity_mode_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + struct wl12xx_vif *wlvif; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 10, &value); + if (ret < 0) { + wl1271_warning("illegal value in diversity mode!"); + return -EINVAL; + } + + /* valid values: 0/1 */ + if (!(value == 0 || value == 1)) { + wl1271_warning("value is not in valid!"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + + wl->diversity_mode = value; + + if (unlikely(wl->state != WLCORE_STATE_ON)) + goto out; + + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) + goto out; + + wl12xx_for_each_wlvif(wl, wlvif) { + wlcore_cmd_generic_cfg(wl, wlvif, + WLCORE_CFG_FEATURE_DIVERSITY_MODE, + wl->diversity_mode, 0); + } + + wl1271_ps_elp_sleep(wl); +out: + mutex_unlock(&wl->mutex); + return count; +} + +static ssize_t diversity_mode_read(struct file *file, + char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(userbuf, count, ppos, + "%d\n", wl->diversity_mode); +} + +static const struct file_operations diversity_mode_ops = { + .write = diversity_mode_write, + .read = diversity_mode_read, + .open = simple_open, + .llseek = default_llseek, +}; + static ssize_t dynamic_fw_traces_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) @@ -511,7 +632,9 @@ int wl18xx_debugfs_add_files(struct wl1271 *wl, DEBUGFS_ADD(conf, moddir); DEBUGFS_ADD(radar_detection, moddir); + DEBUGFS_ADD(radar_debug_mode, moddir); DEBUGFS_ADD(dynamic_fw_traces, moddir); + DEBUGFS_ADD(diversity_mode, moddir); return 0; diff --git a/drivers/net/wireless/ti/wl18xx/event.c b/drivers/net/wireless/ti/wl18xx/event.c index e0bc8a22f98eb..04d65c516122a 100644 --- a/drivers/net/wireless/ti/wl18xx/event.c +++ b/drivers/net/wireless/ti/wl18xx/event.c @@ -146,7 +146,8 @@ int wl18xx_process_mailbox_events(struct wl1271 *wl) mbox->radar_channel, wl18xx_radar_type_decode(mbox->radar_type)); - ieee80211_radar_detected(wl->hw); + if (!wl->radar_debug_mode) + ieee80211_radar_detected(wl->hw); } if (vector & PERIODIC_SCAN_REPORT_EVENT_ID) { @@ -264,6 +265,9 @@ int wl18xx_process_mailbox_events(struct wl1271 *wl) wl->links[link_id].addr), win_size); } + if (vector & FW_LOGGER_INDICATION) + wlcore_event_fw_logger(wl); + out_event: diff --git a/drivers/net/wireless/ti/wl18xx/event.h b/drivers/net/wireless/ti/wl18xx/event.h index 9495fadc80934..d6bfd53f9cd0c 100644 --- a/drivers/net/wireless/ti/wl18xx/event.h +++ b/drivers/net/wireless/ti/wl18xx/event.h @@ -42,6 +42,7 @@ enum { SMART_CONFIG_SYNC_EVENT_ID = BIT(22), SMART_CONFIG_DECODE_EVENT_ID = BIT(23), TIME_SYNC_EVENT_ID = BIT(24), + FW_LOGGER_INDICATION = BIT(25), }; enum wl18xx_radar_types { diff --git a/drivers/net/wireless/ti/wl18xx/main.c b/drivers/net/wireless/ti/wl18xx/main.c index 3d55a5e3df408..2e7b330a9ffd1 100644 --- a/drivers/net/wireless/ti/wl18xx/main.c +++ b/drivers/net/wireless/ti/wl18xx/main.c @@ -461,7 +461,7 @@ static struct wlcore_conf wl18xx_conf = { }, .fwlog = { .mode = WL12XX_FWLOG_CONTINUOUS, - .mem_blocks = 2, + .mem_blocks = 0, .severity = 0, .timestamp = WL12XX_FWLOG_TIMESTAMP_DISABLED, .output = WL12XX_FWLOG_OUTPUT_DBG_PINS, @@ -584,7 +584,7 @@ static const struct wlcore_partition_set wl18xx_ptable[PART_TABLE_LEN] = { .mem = { .start = 0x00A00000, .size = 0x00012000 }, .reg = { .start = 0x00807000, .size = 0x00005000 }, .mem2 = { .start = 0x00800000, .size = 0x0000B000 }, - .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + .mem3 = { .start = 0x00401594, .size = 0x00001020 }, }, [PART_DOWN] = { .mem = { .start = 0x00000000, .size = 0x00014000 }, @@ -602,7 +602,7 @@ static const struct wlcore_partition_set wl18xx_ptable[PART_TABLE_LEN] = { .mem = { .start = 0x00800000, .size = 0x000050FC }, .reg = { .start = 0x00B00404, .size = 0x00001000 }, .mem2 = { .start = 0x00C00000, .size = 0x00000400 }, - .mem3 = { .start = 0x00000000, .size = 0x00000000 }, + .mem3 = { .start = 0x00401594, .size = 0x00001020 }, }, [PART_PHY_INIT] = { .mem = { .start = WL18XX_PHY_INIT_MEM_ADDR, @@ -1030,7 +1030,8 @@ static int wl18xx_boot(struct wl1271 *wl) SMART_CONFIG_SYNC_EVENT_ID | SMART_CONFIG_DECODE_EVENT_ID | RX_BA_WIN_SIZE_CHANGE_EVENT_ID | - TIME_SYNC_EVENT_ID; + TIME_SYNC_EVENT_ID | + FW_LOGGER_INDICATION; wl->ap_event_mask = MAX_TX_FAILURE_EVENT_ID; @@ -1176,6 +1177,13 @@ static int wl18xx_hw_init(struct wl1271 *wl) return ret; } +static int wl18xx_init_vif(struct wl1271 *wl, struct wl12xx_vif *wlvif) +{ + return wlcore_cmd_generic_cfg(wl, wlvif, + WLCORE_CFG_FEATURE_DIVERSITY_MODE, + wl->diversity_mode, 0); +} + static void wl18xx_convert_fw_status(struct wl1271 *wl, void *raw_fw_status, struct wl_fw_status *fw_status) { @@ -1384,24 +1392,24 @@ static int wl18xx_get_pg_ver(struct wl1271 *wl, s8 *ver) #define WL18XX_CONF_FILE_NAME "ti-connectivity/wl18xx-conf.bin" -static int wl18xx_load_conf_file(struct device *dev, struct wlcore_conf *conf, - struct wl18xx_priv_conf *priv_conf) +static struct wlcore_conf_file * +wl18xx_load_conf_file(struct device *dev, const struct firmware **_fw) { struct wlcore_conf_file *conf_file; const struct firmware *fw; int ret; - ret = request_firmware(&fw, WL18XX_CONF_FILE_NAME, dev); + ret = request_firmware(_fw, WL18XX_CONF_FILE_NAME, dev); if (ret < 0) { - wl1271_error("could not get configuration binary %s: %d", + wl1271_info("could not get configuration binary %s: %d", WL18XX_CONF_FILE_NAME, ret); - return ret; + return NULL; } + fw = *_fw; if (fw->size != WL18XX_CONF_SIZE) { - wl1271_error("configuration binary file size is wrong, expected %zu got %zu", + wl1271_info("configuration binary file size is wrong, expected %zu got %zu", WL18XX_CONF_SIZE, fw->size); - ret = -EINVAL; goto out_release; } @@ -1411,7 +1419,6 @@ static int wl18xx_load_conf_file(struct device *dev, struct wlcore_conf *conf, wl1271_error("configuration binary file magic number mismatch, " "expected 0x%0x got 0x%0x", WL18XX_CONF_MAGIC, conf_file->header.magic); - ret = -EINVAL; goto out_release; } @@ -1419,27 +1426,32 @@ static int wl18xx_load_conf_file(struct device *dev, struct wlcore_conf *conf, wl1271_error("configuration binary file version not supported, " "expected 0x%08x got 0x%08x", WL18XX_CONF_VERSION, conf_file->header.version); - ret = -EINVAL; goto out_release; } - memcpy(conf, &conf_file->core, sizeof(*conf)); - memcpy(priv_conf, &conf_file->priv, sizeof(*priv_conf)); + return conf_file; out_release: release_firmware(fw); - return ret; + return NULL; } static int wl18xx_conf_init(struct wl1271 *wl, struct device *dev) { struct wl18xx_priv *priv = wl->priv; + struct wlcore_conf_file *conf_file; + const struct firmware *fw; - if (wl18xx_load_conf_file(dev, &wl->conf, &priv->conf) < 0) { + conf_file = wl18xx_load_conf_file(dev, &fw); + if (conf_file) { + memcpy(&wl->conf, &conf_file->core, sizeof(wl18xx_conf)); + memcpy(&priv->conf, &conf_file->priv, sizeof(priv->conf)); + release_firmware(fw); + } else { wl1271_warning("falling back to default config"); /* apply driver default configuration */ - memcpy(&wl->conf, &wl18xx_conf, sizeof(wl->conf)); + memcpy(&wl->conf, &wl18xx_conf, sizeof(wl18xx_conf)); /* apply default private configuration */ memcpy(&priv->conf, &wl18xx_default_priv_conf, sizeof(priv->conf)); @@ -1713,6 +1725,7 @@ static struct wlcore_ops wl18xx_ops = { .tx_immediate_compl = wl18xx_tx_immediate_completion, .tx_delayed_compl = NULL, .hw_init = wl18xx_hw_init, + .init_vif = wl18xx_init_vif, .convert_fw_status = wl18xx_convert_fw_status, .set_tx_desc_csum = wl18xx_set_tx_desc_csum, .get_pg_ver = wl18xx_get_pg_ver, @@ -1886,6 +1899,28 @@ wl18xx_iface_combinations[] = { BIT(NL80211_CHAN_HT20) | BIT(NL80211_CHAN_HT40MINUS) | BIT(NL80211_CHAN_HT40PLUS), + }, + { + .max_interfaces = 3, + .limits = wl18xx_iface_ap_cl_limits, + .n_limits = ARRAY_SIZE(wl18xx_iface_ap_cl_limits), + .num_different_channels = 2, + }, + { + .max_interfaces = 3, + .limits = wl18xx_iface_ap_cl_limits, + .n_limits = ARRAY_SIZE(wl18xx_iface_ap_cl_limits), + .num_different_channels = 1, + .radar_detect_widths = BIT(NL80211_CHAN_NO_HT) | + BIT(NL80211_CHAN_HT20) | + BIT(NL80211_CHAN_HT40MINUS) | + BIT(NL80211_CHAN_HT40PLUS), + }, + { + .max_interfaces = 3, + .limits = wl18xx_iface_ap_go_limits, + .n_limits = ARRAY_SIZE(wl18xx_iface_ap_go_limits), + .num_different_channels = 1, } }; @@ -2005,10 +2040,8 @@ static int wl18xx_setup(struct wl1271 *wl) &wl18xx_siso20_ht_cap); } - if (!checksum_param) { + if (!checksum_param) wl18xx_ops.set_rx_csum = NULL; - wl18xx_ops.init_vif = NULL; - } /* Enable 11a Band only if we have 5G antennas */ wl->enable_11a = (priv->conf.phy.number_of_assembled_ant5 != 0); diff --git a/drivers/net/wireless/ti/wlcore/cmd.h b/drivers/net/wireless/ti/wlcore/cmd.h index 8dc46c0a489a1..deb84757f2ded 100644 --- a/drivers/net/wireless/ti/wlcore/cmd.h +++ b/drivers/net/wireless/ti/wlcore/cmd.h @@ -626,7 +626,6 @@ struct wl12xx_cmd_remove_peer { */ enum wl12xx_fwlogger_log_mode { WL12XX_FWLOG_CONTINUOUS, - WL12XX_FWLOG_ON_DEMAND }; /* Include/exclude timestamps from the log messages */ @@ -655,6 +654,7 @@ struct wl12xx_cmd_regdomain_dfs_config { } __packed; enum wlcore_generic_cfg_feature { + WLCORE_CFG_FEATURE_DIVERSITY_MODE = 1, WLCORE_CFG_FEATURE_RADAR_DEBUG = 2, }; diff --git a/drivers/net/wireless/ti/wlcore/debugfs.c b/drivers/net/wireless/ti/wlcore/debugfs.c index eb43f94a15973..cbf1eb04ad36c 100644 --- a/drivers/net/wireless/ti/wlcore/debugfs.c +++ b/drivers/net/wireless/ti/wlcore/debugfs.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "wlcore.h" #include "debug.h" @@ -1234,6 +1235,99 @@ static const struct file_operations dev_mem_ops = { .llseek = dev_mem_seek, }; +static ssize_t irq_type_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + const char *wlcore_irq_type; + + switch (wl->irq_flags & IRQF_TRIGGER_MASK) { + case IRQF_TRIGGER_RISING: + wlcore_irq_type = "RISING EDGE"; + break; + case IRQF_TRIGGER_FALLING: + wlcore_irq_type = "FALLING EDGE"; + break; + case IRQF_TRIGGER_HIGH: + wlcore_irq_type = "LEVEL HIGH"; + break; + case IRQF_TRIGGER_LOW: + wlcore_irq_type = "LEVEL LOW"; + break; + default: + wlcore_irq_type = "NONE"; + } + + return wl1271_format_buffer(user_buf, count, + ppos, "%s\n", + wlcore_irq_type); +} + +static const struct file_operations irq_type_ops = { + .read = irq_type_read, + .open = simple_open, + .llseek = default_llseek, +}; + +static ssize_t fw_logger_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + + return wl1271_format_buffer(user_buf, count, + ppos, "%d\n", + wl->conf.fwlog.output); +} + +static ssize_t fw_logger_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wl1271 *wl = file->private_data; + unsigned long value; + int ret; + + ret = kstrtoul_from_user(user_buf, count, 0, &value); + if (ret < 0) { + wl1271_warning("illegal value in fw_logger"); + return -EINVAL; + } + + if ((value > 2) || (value == 0)) { + wl1271_warning("fw_logger value must be 1-UART 2-SDIO"); + return -ERANGE; + } + + if (wl->conf.fwlog.output == 0) { + wl1271_warning("iligal opperation - fw logger disabled by default, please change mode via wlconf"); + return -EINVAL; + } + + mutex_lock(&wl->mutex); + ret = wl1271_ps_elp_wakeup(wl); + if (ret < 0) { + count = ret; + goto out; + } + + wl->conf.fwlog.output = value; + + ret = wl12xx_cmd_config_fwlog(wl); + + wl1271_ps_elp_sleep(wl); + +out: + mutex_unlock(&wl->mutex); + return count; +} + +static const struct file_operations fw_logger_ops = { + .open = simple_open, + .read = fw_logger_read, + .write = fw_logger_write, + .llseek = default_llseek, +}; + static int wl1271_debugfs_add_files(struct wl1271 *wl, struct dentry *rootdir) { @@ -1260,6 +1354,8 @@ static int wl1271_debugfs_add_files(struct wl1271 *wl, DEBUGFS_ADD(irq_timeout, rootdir); DEBUGFS_ADD(fw_stats_raw, rootdir); DEBUGFS_ADD(sleep_auth, rootdir); + DEBUGFS_ADD(irq_type, rootdir); + DEBUGFS_ADD(fw_logger, rootdir); streaming = debugfs_create_dir("rx_streaming", rootdir); if (!streaming || IS_ERR(streaming)) diff --git a/drivers/net/wireless/ti/wlcore/event.c b/drivers/net/wireless/ti/wlcore/event.c index c42e78955e7b3..a613febcf5469 100644 --- a/drivers/net/wireless/ti/wlcore/event.c +++ b/drivers/net/wireless/ti/wlcore/event.c @@ -28,6 +28,88 @@ #include "ps.h" #include "scan.h" #include "wl12xx_80211.h" +#include "hw_ops.h" + +#define WL18XX_LOGGER_SDIO_BUFF_MAX (0x1020) +#define WL18XX_DATA_RAM_BASE_ADDRESS (0x20000000) +#define WL18XX_LOGGER_SDIO_BUFF_ADDR (0x40159c) +#define WL18XX_LOGGER_BUFF_OFFSET (sizeof(struct fw_logger_information)) +#define WL18XX_LOGGER_READ_POINT_OFFSET (12) + +int wlcore_event_fw_logger(struct wl1271 *wl) +{ + u32 ret; + struct fw_logger_information fw_log; + u8 *buffer; + u32 internal_fw_addrbase = WL18XX_DATA_RAM_BASE_ADDRESS; + u32 addr = WL18XX_LOGGER_SDIO_BUFF_ADDR; + u32 end_buff_addr = WL18XX_LOGGER_SDIO_BUFF_ADDR + + WL18XX_LOGGER_BUFF_OFFSET; + u32 available_len; + u32 actual_len; + u32 clear_addr; + size_t len; + u32 start_loc; + + buffer = kzalloc(WL18XX_LOGGER_SDIO_BUFF_MAX, GFP_KERNEL); + if (!buffer) { + wl1271_error("Fail to allocate fw logger memory"); + fw_log.actual_buff_size = cpu_to_le32(0); + goto out; + } + + ret = wlcore_read(wl, addr, buffer, WL18XX_LOGGER_SDIO_BUFF_MAX, + false); + if (ret < 0) { + wl1271_error("Fail to read logger buffer, error_id = %d", + ret); + fw_log.actual_buff_size = cpu_to_le32(0); + goto free_out; + } + + memcpy(&fw_log, buffer, sizeof(fw_log)); + + if (le32_to_cpu(fw_log.actual_buff_size) == 0) + goto free_out; + + actual_len = le32_to_cpu(fw_log.actual_buff_size); + start_loc = (le32_to_cpu(fw_log.buff_read_ptr) - internal_fw_addrbase) - + addr; + end_buff_addr += le32_to_cpu(fw_log.max_buff_size); + available_len = end_buff_addr - + (le32_to_cpu(fw_log.buff_read_ptr) - + internal_fw_addrbase); + actual_len = min(actual_len, available_len); + len = actual_len; + + wl12xx_copy_fwlog(wl, &buffer[start_loc], len); + clear_addr = addr + start_loc + le32_to_cpu(fw_log.actual_buff_size) + + internal_fw_addrbase; + + len = le32_to_cpu(fw_log.actual_buff_size) - len; + if (len) { + wl12xx_copy_fwlog(wl, + &buffer[WL18XX_LOGGER_BUFF_OFFSET], + len); + clear_addr = addr + WL18XX_LOGGER_BUFF_OFFSET + len + + internal_fw_addrbase; + } + + /* double check that clear address and write pointer are the same */ + if (clear_addr != le32_to_cpu(fw_log.buff_write_ptr)) { + wl1271_error("Calculate of clear addr Clear = %x, write = %x", + clear_addr, le32_to_cpu(fw_log.buff_write_ptr)); + } + + /* indicate FW about Clear buffer */ + ret = wlcore_write32(wl, addr + WL18XX_LOGGER_READ_POINT_OFFSET, + fw_log.buff_write_ptr); +free_out: + kfree(buffer); +out: + return le32_to_cpu(fw_log.actual_buff_size); +} +EXPORT_SYMBOL_GPL(wlcore_event_fw_logger); void wlcore_event_rssi_trigger(struct wl1271 *wl, s8 *metric_arr) { diff --git a/drivers/net/wireless/ti/wlcore/event.h b/drivers/net/wireless/ti/wlcore/event.h index acc7a59d3828f..75e8e98da2fe6 100644 --- a/drivers/net/wireless/ti/wlcore/event.h +++ b/drivers/net/wireless/ti/wlcore/event.h @@ -64,6 +64,14 @@ enum { #define NUM_OF_RSSI_SNR_TRIGGERS 8 +struct fw_logger_information { + __le32 max_buff_size; + __le32 actual_buff_size; + __le32 num_trace_drop; + __le32 buff_read_ptr; + __le32 buff_write_ptr; +} __packed; + struct wl1271; int wl1271_event_unmask(struct wl1271 *wl); @@ -84,4 +92,5 @@ void wlcore_event_max_tx_failure(struct wl1271 *wl, unsigned long sta_bitmap); void wlcore_event_inactive_sta(struct wl1271 *wl, unsigned long sta_bitmap); void wlcore_event_roc_complete(struct wl1271 *wl); void wlcore_event_rssi_trigger(struct wl1271 *wl, s8 *metric_arr); +int wlcore_event_fw_logger(struct wl1271 *wl); #endif diff --git a/drivers/net/wireless/ti/wlcore/init.c b/drivers/net/wireless/ti/wlcore/init.c index e92f2639af2c8..2307671a6bb63 100644 --- a/drivers/net/wireless/ti/wlcore/init.c +++ b/drivers/net/wireless/ti/wlcore/init.c @@ -558,6 +558,12 @@ static int wl12xx_init_ap_role(struct wl1271 *wl, struct wl12xx_vif *wlvif) if (ret < 0) return ret; + + if (wl->radar_debug_mode) + wlcore_cmd_generic_cfg(wl, wlvif, + WLCORE_CFG_FEATURE_RADAR_DEBUG, + wl->radar_debug_mode, 0); + return 0; } diff --git a/drivers/net/wireless/ti/wlcore/io.c b/drivers/net/wireless/ti/wlcore/io.c index 68e74eefd296e..9ac118e727e94 100644 --- a/drivers/net/wireless/ti/wlcore/io.c +++ b/drivers/net/wireless/ti/wlcore/io.c @@ -175,12 +175,13 @@ int wlcore_set_partition(struct wl1271 *wl, if (ret < 0) goto out; - /* - * We don't need the size of the last partition, as it is - * automatically calculated based on the total memory size and - * the sizes of the previous partitions. - */ ret = wlcore_raw_write32(wl, HW_PART3_START_ADDR, p->mem3.start); + if (ret < 0) + goto out; + + ret = wlcore_raw_write32(wl, HW_PART3_SIZE_ADDR, p->mem3.size); + if (ret < 0) + goto out; out: return ret; diff --git a/drivers/net/wireless/ti/wlcore/io.h b/drivers/net/wireless/ti/wlcore/io.h index 10cf3747694d5..6c257b54f4150 100644 --- a/drivers/net/wireless/ti/wlcore/io.h +++ b/drivers/net/wireless/ti/wlcore/io.h @@ -36,8 +36,8 @@ #define HW_PART1_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 12) #define HW_PART2_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 16) #define HW_PART2_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 20) -#define HW_PART3_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 24) - +#define HW_PART3_SIZE_ADDR (HW_PARTITION_REGISTERS_ADDR + 24) +#define HW_PART3_START_ADDR (HW_PARTITION_REGISTERS_ADDR + 28) #define HW_ACCESS_REGISTER_SIZE 4 #define HW_ACCESS_PRAM_MAX_RANGE 0x3c000 diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index 012a9039b7ef6..25d420021266a 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -1,4 +1,3 @@ - /* * This file is part of wlcore * @@ -303,25 +302,11 @@ static void wl12xx_tx_watchdog_work(struct work_struct *work) static void wlcore_adjust_conf(struct wl1271 *wl) { - /* Adjust settings according to optional module parameters */ - - /* Firmware Logger params */ - if (fwlog_mem_blocks != -1) { - if (fwlog_mem_blocks >= CONF_FWLOG_MIN_MEM_BLOCKS && - fwlog_mem_blocks <= CONF_FWLOG_MAX_MEM_BLOCKS) { - wl->conf.fwlog.mem_blocks = fwlog_mem_blocks; - } else { - wl1271_error( - "Illegal fwlog_mem_blocks=%d using default %d", - fwlog_mem_blocks, wl->conf.fwlog.mem_blocks); - } - } if (fwlog_param) { if (!strcmp(fwlog_param, "continuous")) { wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; - } else if (!strcmp(fwlog_param, "ondemand")) { - wl->conf.fwlog.mode = WL12XX_FWLOG_ON_DEMAND; + wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_HOST; } else if (!strcmp(fwlog_param, "dbgpins")) { wl->conf.fwlog.mode = WL12XX_FWLOG_CONTINUOUS; wl->conf.fwlog.output = WL12XX_FWLOG_OUTPUT_DBG_PINS; @@ -674,6 +659,10 @@ static irqreturn_t wlcore_irq(int irq, void *cookie) wl1271_debug(DEBUG_IRQ, "should not enqueue work"); disable_irq_nosync(wl->irq); pm_wakeup_event(wl->dev, 0); +#ifdef CONFIG_HAS_WAKELOCK + if (!test_and_set_bit(WL1271_FLAG_WAKE_LOCK, &wl->flags)) + wake_lock(&wl->wake_lock); +#endif spin_unlock_irqrestore(&wl->wl_lock, flags); return IRQ_HANDLED; } @@ -695,6 +684,11 @@ static irqreturn_t wlcore_irq(int irq, void *cookie) if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags) && wl1271_tx_total_queue_count(wl) > 0) ieee80211_queue_work(wl->hw, &wl->tx_work); + +#ifdef CONFIG_HAS_WAKELOCK + if (test_and_clear_bit(WL1271_FLAG_WAKE_LOCK, &wl->flags)) + wake_unlock(&wl->wake_lock); +#endif spin_unlock_irqrestore(&wl->wl_lock, flags); mutex_unlock(&wl->mutex); @@ -805,6 +799,10 @@ void wl12xx_queue_recovery_work(struct wl1271 *wl) set_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags); wl1271_ps_elp_wakeup(wl); wlcore_disable_interrupts_nosync(wl); +#ifdef CONFIG_HAS_WAKELOCK + /* give us a grace period for recovery */ + wake_lock_timeout(&wl->recovery_wake, 5 * HZ); +#endif ieee80211_queue_work(wl->hw, &wl->recovery_work); } } @@ -825,91 +823,32 @@ size_t wl12xx_copy_fwlog(struct wl1271 *wl, u8 *memblock, size_t maxlen) static void wl12xx_read_fwlog_panic(struct wl1271 *wl) { - struct wlcore_partition_set part, old_part; - u32 addr; - u32 offset; - u32 end_of_log; - u8 *block; - int ret; + u32 end_of_log = 0; - if ((wl->quirks & WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED) || - (wl->conf.fwlog.mem_blocks == 0)) + if (wl->quirks & WLCORE_QUIRK_FWLOG_NOT_IMPLEMENTED) return; wl1271_info("Reading FW panic log"); - block = kmalloc(wl->fw_mem_block_size, GFP_KERNEL); - if (!block) - return; - /* * Make sure the chip is awake and the logger isn't active. * Do not send a stop fwlog command if the fw is hanged or if * dbgpins are used (due to some fw bug). */ if (wl1271_ps_elp_wakeup(wl)) - goto out; + return; if (!wl->watchdog_recovery && wl->conf.fwlog.output != WL12XX_FWLOG_OUTPUT_DBG_PINS) wl12xx_cmd_stop_fwlog(wl); - /* Read the first memory block address */ - ret = wlcore_fw_status(wl, wl->fw_status); - if (ret < 0) - goto out; - - addr = wl->fw_status->log_start_addr; - if (!addr) - goto out; - - if (wl->conf.fwlog.mode == WL12XX_FWLOG_CONTINUOUS) { - offset = sizeof(addr) + sizeof(struct wl1271_rx_descriptor); - end_of_log = wl->fwlog_end; - } else { - offset = sizeof(addr); - end_of_log = addr; - } - - old_part = wl->curr_part; - memset(&part, 0, sizeof(part)); - /* Traverse the memory blocks linked list */ do { - part.mem.start = wlcore_hw_convert_hwaddr(wl, addr); - part.mem.size = PAGE_SIZE; - - ret = wlcore_set_partition(wl, &part); - if (ret < 0) { - wl1271_error("%s: set_partition start=0x%X size=%d", - __func__, part.mem.start, part.mem.size); - goto out; + end_of_log = wlcore_event_fw_logger(wl); + if (end_of_log == 0) { + msleep(100); + end_of_log = wlcore_event_fw_logger(wl); } - - memset(block, 0, wl->fw_mem_block_size); - ret = wlcore_read_hwaddr(wl, addr, block, - wl->fw_mem_block_size, false); - - if (ret < 0) - goto out; - - /* - * Memory blocks are linked to one another. The first 4 bytes - * of each memory block hold the hardware address of the next - * one. The last memory block points to the first one in - * on demand mode and is equal to 0x2000000 in continuous mode. - */ - addr = le32_to_cpup((__le32 *)block); - - if (!wl12xx_copy_fwlog(wl, block + offset, - wl->fw_mem_block_size - offset)) - break; - } while (addr && (addr != end_of_log)); - - wake_up_interruptible(&wl->fwlog_waitq); - -out: - kfree(block); - wlcore_set_partition(wl, &old_part); + } while (end_of_log != 0); } static void wlcore_save_freed_pkts(struct wl1271 *wl, struct wl12xx_vif *wlvif, @@ -1777,6 +1716,10 @@ static int wl1271_op_suspend(struct ieee80211_hw *hw, /* we want to perform the recovery before suspending */ if (test_bit(WL1271_FLAG_RECOVERY_IN_PROGRESS, &wl->flags)) { wl1271_warning("postponing suspend to perform recovery"); +#ifdef CONFIG_HAS_WAKELOCK + /* give us a grace period for recovery */ + wake_lock_timeout(&wl->recovery_wake, 5 * HZ); +#endif return -EBUSY; } @@ -1923,6 +1866,12 @@ static int wl1271_op_resume(struct ieee80211_hw *hw) wl1271_ps_elp_sleep(wl); out: +#ifdef CONFIG_HAS_WAKELOCK + spin_lock_irqsave(&wl->wl_lock, flags); + if (test_and_clear_bit(WL1271_FLAG_WAKE_LOCK, &wl->flags)) + wake_unlock(&wl->wake_lock); + spin_unlock_irqrestore(&wl->wl_lock, flags); +#endif wl->wow_enabled = false; /* @@ -5631,9 +5580,10 @@ static int wlcore_op_remain_on_channel(struct ieee80211_hw *hw, goto out; /* return EBUSY if we can't ROC right now */ - if (WARN_ON(wl->roc_vif || - find_first_bit(wl->roc_map, - WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES)) { + if (wl->roc_vif || + find_first_bit(wl->roc_map, WL12XX_MAX_ROLES) < WL12XX_MAX_ROLES) { + wl1271_warning("active roc on role %d", + find_first_bit(wl->roc_map, WL12XX_MAX_ROLES)); ret = -EBUSY; goto out; } @@ -6341,7 +6291,6 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, wl->active_sta_count = 0; wl->active_link_count = 0; wl->fwlog_size = 0; - init_waitqueue_head(&wl->fwlog_waitq); /* The system link is always allocated */ __set_bit(WL12XX_SYSTEM_HLID, wl->links_map); @@ -6351,6 +6300,11 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, wl->tx_frames[i] = NULL; spin_lock_init(&wl->wl_lock); +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_init(&wl->wake_lock, WAKE_LOCK_SUSPEND, "wl1271_wake"); + wake_lock_init(&wl->rx_wake, WAKE_LOCK_SUSPEND, "rx_wake"); + wake_lock_init(&wl->recovery_wake, WAKE_LOCK_SUSPEND, "recovery_wake"); +#endif wl->state = WLCORE_STATE_OFF; wl->fw_type = WL12XX_FW_TYPE_NONE; @@ -6407,6 +6361,11 @@ struct ieee80211_hw *wlcore_alloc_hw(size_t priv_size, u32 aggr_buf_size, free_pages((unsigned long)wl->aggr_buf, order); err_wq: +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_destroy(&wl->wake_lock); + wake_lock_destroy(&wl->rx_wake); + wake_lock_destroy(&wl->recovery_wake); +#endif destroy_workqueue(wl->freezable_wq); err_hw: @@ -6424,10 +6383,14 @@ EXPORT_SYMBOL_GPL(wlcore_alloc_hw); int wlcore_free_hw(struct wl1271 *wl) { +#ifdef CONFIG_HAS_WAKELOCK + wake_lock_destroy(&wl->wake_lock); + wake_lock_destroy(&wl->rx_wake); + wake_lock_destroy(&wl->recovery_wake); +#endif /* Unblock any fwlog readers */ mutex_lock(&wl->mutex); wl->fwlog_size = -1; - wake_up_interruptible_all(&wl->fwlog_waitq); mutex_unlock(&wl->mutex); wlcore_sysfs_free(wl); @@ -6634,7 +6597,7 @@ MODULE_PARM_DESC(debug_level, "wl12xx debugging level"); module_param_named(fwlog, fwlog_param, charp, 0); MODULE_PARM_DESC(fwlog, - "FW logger options: continuous, ondemand, dbgpins or disable"); + "FW logger options: continuous, dbgpins or disable"); module_param(fwlog_mem_blocks, int, S_IRUSR | S_IWUSR); MODULE_PARM_DESC(fwlog_mem_blocks, "fwlog mem_blocks"); diff --git a/drivers/net/wireless/ti/wlcore/rx.c b/drivers/net/wireless/ti/wlcore/rx.c index 7df672a84530b..20ae63b571ea5 100644 --- a/drivers/net/wireless/ti/wlcore/rx.c +++ b/drivers/net/wireless/ti/wlcore/rx.c @@ -74,6 +74,13 @@ static void wl1271_rx_status(struct wl1271 *wl, if (desc->rate <= wl->hw_min_ht_rate) status->flag |= RX_FLAG_HT; + /* + * Read the signal level and antenna diversity indication. + * The msb in the signal level is always set as it is a + * negative number. + * The antenna indication is the msb of the rssi. + */ + status->signal = ((desc->rssi & RSSI_LEVEL_BITMASK) | BIT(7)); status->antenna = ((desc->rssi & ANT_DIVERSITY_BITMASK) >> 7); @@ -143,7 +150,6 @@ static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length, if (desc->packet_class == WL12XX_RX_CLASS_LOGGER) { size_t len = length - sizeof(*desc); wl12xx_copy_fwlog(wl, data + sizeof(*desc), len); - wake_up_interruptible(&wl->fwlog_waitq); return 0; } @@ -201,6 +207,11 @@ static int wl1271_rx_handle_data(struct wl1271 *wl, u8 *data, u32 length, skb_queue_tail(&wl->deferred_rx_queue, skb); queue_work(wl->freezable_wq, &wl->netstack_work); +#ifdef CONFIG_HAS_WAKELOCK + /* let the frame some time to propagate to user-space */ + wake_lock_timeout(&wl->rx_wake, HZ); +#endif + return is_data; } diff --git a/drivers/net/wireless/ti/wlcore/sdio.c b/drivers/net/wireless/ti/wlcore/sdio.c index f50e466b5ce26..8cbde7b51a495 100644 --- a/drivers/net/wireless/ti/wlcore/sdio.c +++ b/drivers/net/wireless/ti/wlcore/sdio.c @@ -235,6 +235,9 @@ static int wlcore_probe_of(struct device *dev, int *irq, { struct device_node *np = dev->of_node; + if (!np) + np = of_find_matching_node(NULL, wlcore_sdio_of_match_table); + if (!np || !of_match_node(wlcore_sdio_of_match_table, np)) return -ENODATA; diff --git a/drivers/net/wireless/ti/wlcore/spi.c b/drivers/net/wireless/ti/wlcore/spi.c index 720e4e4b5a3cd..f1ac2839d97cb 100644 --- a/drivers/net/wireless/ti/wlcore/spi.c +++ b/drivers/net/wireless/ti/wlcore/spi.c @@ -73,10 +73,7 @@ */ #define SPI_AGGR_BUFFER_SIZE (4 * PAGE_SIZE) -/* Maximum number of SPI write chunks */ -#define WSPI_MAX_NUM_OF_CHUNKS \ - ((SPI_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) + 1) - +#define WSPI_MAX_NUM_OF_CHUNKS (SPI_AGGR_BUFFER_SIZE / WSPI_MAX_CHUNK_SIZE) struct wl12xx_spi_glue { struct device *dev; @@ -271,10 +268,9 @@ static int __must_check wl12xx_spi_raw_write(struct device *child, int addr, void *buf, size_t len, bool fixed) { struct wl12xx_spi_glue *glue = dev_get_drvdata(child->parent); - /* SPI write buffers - 2 for each chunk */ - struct spi_transfer t[2 * WSPI_MAX_NUM_OF_CHUNKS]; + struct spi_transfer t[2 * (WSPI_MAX_NUM_OF_CHUNKS + 1)]; struct spi_message m; - u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; /* 1 command per chunk */ + u32 commands[WSPI_MAX_NUM_OF_CHUNKS]; u32 *cmd; u32 chunk_len; int i; diff --git a/drivers/net/wireless/ti/wlcore/sysfs.c b/drivers/net/wireless/ti/wlcore/sysfs.c index 24dd288d68098..a9218e5b0efcf 100644 --- a/drivers/net/wireless/ti/wlcore/sysfs.c +++ b/drivers/net/wireless/ti/wlcore/sysfs.c @@ -119,32 +119,6 @@ static ssize_t wl1271_sysfs_read_fwlog(struct file *filp, struct kobject *kobj, if (ret < 0) return -ERESTARTSYS; - /* Let only one thread read the log at a time, blocking others */ - while (wl->fwlog_size == 0) { - DEFINE_WAIT(wait); - - prepare_to_wait_exclusive(&wl->fwlog_waitq, - &wait, - TASK_INTERRUPTIBLE); - - if (wl->fwlog_size != 0) { - finish_wait(&wl->fwlog_waitq, &wait); - break; - } - - mutex_unlock(&wl->mutex); - - schedule(); - finish_wait(&wl->fwlog_waitq, &wait); - - if (signal_pending(current)) - return -ERESTARTSYS; - - ret = mutex_lock_interruptible(&wl->mutex); - if (ret < 0) - return -ERESTARTSYS; - } - /* Check if the fwlog is still valid */ if (wl->fwlog_size < 0) { mutex_unlock(&wl->mutex); diff --git a/drivers/net/wireless/ti/wlcore/wlcore.h b/drivers/net/wireless/ti/wlcore/wlcore.h index a1b6040e64912..aa61f3501e822 100644 --- a/drivers/net/wireless/ti/wlcore/wlcore.h +++ b/drivers/net/wireless/ti/wlcore/wlcore.h @@ -310,9 +310,6 @@ struct wl1271 { /* FW memory block size */ u32 fw_mem_block_size; - /* Sysfs FW log entry readers wait queue */ - wait_queue_head_t fwlog_waitq; - /* Hardware recovery work */ struct work_struct recovery_work; bool watchdog_recovery; @@ -353,6 +350,11 @@ struct wl1271 { /* in dBm */ int power_level; +#ifdef CONFIG_HAS_WAKELOCK + struct wake_lock wake_lock; + struct wake_lock rx_wake; + struct wake_lock recovery_wake; +#endif struct wl1271_stats stats; __le32 *buffer_32; @@ -466,6 +468,7 @@ struct wl1271 { /* the current dfs region */ enum nl80211_dfs_regions dfs_region; + bool radar_debug_mode; /* size of the private FW status data */ size_t fw_status_len; @@ -503,6 +506,9 @@ struct wl1271 { /* dynamic fw traces */ u32 dynamic_fw_traces; + + /* antenna diversity shut down */ + u8 diversity_mode; }; int wlcore_probe(struct wl1271 *wl, struct platform_device *pdev); diff --git a/drivers/net/wireless/ti/wlcore/wlcore_i.h b/drivers/net/wireless/ti/wlcore/wlcore_i.h index f5374ee82dbb0..d910a1b23e028 100644 --- a/drivers/net/wireless/ti/wlcore/wlcore_i.h +++ b/drivers/net/wireless/ti/wlcore/wlcore_i.h @@ -31,6 +31,9 @@ #include #include #include +#ifdef CONFIG_HAS_WAKELOCK +#include +#endif #include "conf.h" #include "ini.h" @@ -228,6 +231,7 @@ enum wl12xx_flags { WL1271_FLAG_TX_PENDING, WL1271_FLAG_IN_ELP, WL1271_FLAG_ELP_REQUESTED, + WL1271_FLAG_WAKE_LOCK, WL1271_FLAG_IRQ_RUNNING, WL1271_FLAG_FW_TX_BUSY, WL1271_FLAG_DUMMY_PACKET_PENDING, diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig new file mode 100644 index 0000000000000..bc4ea585b42e7 --- /dev/null +++ b/drivers/nvmem/Kconfig @@ -0,0 +1,81 @@ +menuconfig NVMEM + tristate "NVMEM Support" + select REGMAP + help + Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES... + + This framework is designed to provide a generic interface to NVMEM + from both the Linux Kernel and the userspace. + + This driver can also be built as a module. If so, the module + will be called nvmem_core. + + If unsure, say no. + +if NVMEM + +config NVMEM_IMX_OCOTP + tristate "i.MX6 On-Chip OTP Controller support" + depends on SOC_IMX6 + help + This is a driver for the On-Chip OTP Controller (OCOTP) available on + i.MX6 SoCs, providing access to 4 Kbits of one-time programmable + eFuses. + + This driver can also be built as a module. If so, the module + will be called nvmem-imx-ocotp. + +config NVMEM_MXS_OCOTP + tristate "Freescale MXS On-Chip OTP Memory Support" + depends on ARCH_MXS || COMPILE_TEST + help + If you say Y here, you will get readonly access to the + One Time Programmable memory pages that are stored + on the Freescale i.MX23/i.MX28 processor. + + This driver can also be built as a module. If so, the module + will be called nvmem-mxs-ocotp. + +config QCOM_QFPROM + tristate "QCOM QFPROM Support" + depends on ARCH_QCOM || COMPILE_TEST + select REGMAP_MMIO + help + Say y here to enable QFPROM support. The QFPROM provides access + functions for QFPROM data to rest of the drivers via nvmem interface. + + This driver can also be built as a module. If so, the module + will be called nvmem_qfprom. + +config ROCKCHIP_EFUSE + tristate "Rockchip eFuse Support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + help + This is a simple drive to dump specified values of Rockchip SoC + from eFuse, such as cpu-leakage. + + This driver can also be built as a module. If so, the module + will be called nvmem_rockchip_efuse. + +config NVMEM_SUNXI_SID + tristate "Allwinner SoCs SID support" + depends on ARCH_SUNXI + select REGMAP_MMIO + help + This is a driver for the 'security ID' available on various Allwinner + devices. + + This driver can also be built as a module. If so, the module + will be called nvmem_sunxi_sid. + +config NVMEM_VF610_OCOTP + tristate "VF610 SoC OCOTP support" + depends on SOC_VF610 || COMPILE_TEST + help + This is a driver for the 'OCOTP' peripheral available on Vybrid + devices like VF5xx and VF6xx. + + This driver can also be build as a module. If so, the module will + be called nvmem-vf610-ocotp. + +endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile new file mode 100644 index 0000000000000..95dde3f8f0850 --- /dev/null +++ b/drivers/nvmem/Makefile @@ -0,0 +1,20 @@ +# +# Makefile for nvmem drivers. +# + +obj-$(CONFIG_NVMEM) += nvmem_core.o +nvmem_core-y := core.o + +# Devices +obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o +nvmem-imx-ocotp-y := imx-ocotp.o +obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o +nvmem-mxs-ocotp-y := mxs-ocotp.o +obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o +nvmem_qfprom-y := qfprom.o +obj-$(CONFIG_ROCKCHIP_EFUSE) += nvmem_rockchip_efuse.o +nvmem_rockchip_efuse-y := rockchip-efuse.o +obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o +nvmem_sunxi_sid-y := sunxi_sid.o +obj-$(CONFIG_NVMEM_VF610_OCOTP) += nvmem-vf610-ocotp.o +nvmem-vf610-ocotp-y := vf610-ocotp.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c new file mode 100644 index 0000000000000..95e013216681b --- /dev/null +++ b/drivers/nvmem/core.c @@ -0,0 +1,1052 @@ +/* + * nvmem framework core. + * + * Copyright (C) 2015 Srinivas Kandagatla + * Copyright (C) 2013 Maxime Ripard + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct nvmem_device { + const char *name; + struct regmap *regmap; + struct module *owner; + struct device dev; + struct bin_attribute bin; + int stride; + int word_size; + int ncells; + int id; + int users; + size_t size; + bool read_only; +}; + +struct nvmem_cell { + const char *name; + int offset; + int bytes; + int bit_offset; + int nbits; + struct nvmem_device *nvmem; + struct list_head node; +}; + +static DEFINE_MUTEX(nvmem_mutex); +static DEFINE_IDA(nvmem_ida); + +static LIST_HEAD(nvmem_cells); +static DEFINE_MUTEX(nvmem_cells_mutex); + +#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); + int rc; + + /* Stop the user from reading */ + if (pos >= nvmem->size) + return 0; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = round_down(count, nvmem->word_size); + + rc = regmap_raw_read(nvmem->regmap, pos, buf, count); + + if (IS_ERR_VALUE(rc)) + return rc; + + return count; +} + +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); + int rc; + + /* Stop the user from writing */ + if (pos >= nvmem->size) + return 0; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = round_down(count, nvmem->word_size); + + rc = regmap_raw_write(nvmem->regmap, pos, buf, count); + + if (IS_ERR_VALUE(rc)) + return rc; + + return count; +} + +/* default read/write permissions */ +static struct bin_attribute bin_attr_template = { + .attr = { + .name = "nvmem", + .mode = S_IRUSR, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +static void nvmem_release(struct device *dev) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + ida_simple_remove(&nvmem_ida, nvmem->id); + kfree(nvmem); +} + +static const struct device_type nvmem_provider_type = { + .release = nvmem_release, +}; + +static struct bus_type nvmem_bus_type = { + .name = "nvmem", +}; + +static int of_nvmem_match(struct device *dev, void *nvmem_np) +{ + return dev->of_node == nvmem_np; +} + +static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np) +{ + struct device *d; + + if (!nvmem_np) + return NULL; + + d = bus_find_device(&nvmem_bus_type, NULL, nvmem_np, of_nvmem_match); + + if (!d) + return NULL; + + return to_nvmem_device(d); +} + +static struct nvmem_cell *nvmem_find_cell(const char *cell_id) +{ + struct nvmem_cell *p; + + list_for_each_entry(p, &nvmem_cells, node) + if (p && !strcmp(p->name, cell_id)) + return p; + + return NULL; +} + +static void nvmem_cell_drop(struct nvmem_cell *cell) +{ + mutex_lock(&nvmem_cells_mutex); + list_del(&cell->node); + mutex_unlock(&nvmem_cells_mutex); + kfree(cell); +} + +static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem) +{ + struct nvmem_cell *cell; + struct list_head *p, *n; + + list_for_each_safe(p, n, &nvmem_cells) { + cell = list_entry(p, struct nvmem_cell, node); + if (cell->nvmem == nvmem) + nvmem_cell_drop(cell); + } +} + +static void nvmem_cell_add(struct nvmem_cell *cell) +{ + mutex_lock(&nvmem_cells_mutex); + list_add_tail(&cell->node, &nvmem_cells); + mutex_unlock(&nvmem_cells_mutex); +} + +static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem, + const struct nvmem_cell_info *info, + struct nvmem_cell *cell) +{ + cell->nvmem = nvmem; + cell->offset = info->offset; + cell->bytes = info->bytes; + cell->name = info->name; + + cell->bit_offset = info->bit_offset; + cell->nbits = info->nbits; + + if (cell->nbits) + cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, + BITS_PER_BYTE); + + if (!IS_ALIGNED(cell->offset, nvmem->stride)) { + dev_err(&nvmem->dev, + "cell %s unaligned to nvmem stride %d\n", + cell->name, nvmem->stride); + return -EINVAL; + } + + return 0; +} + +static int nvmem_add_cells(struct nvmem_device *nvmem, + const struct nvmem_config *cfg) +{ + struct nvmem_cell **cells; + const struct nvmem_cell_info *info = cfg->cells; + int i, rval; + + cells = kcalloc(cfg->ncells, sizeof(*cells), GFP_KERNEL); + if (!cells) + return -ENOMEM; + + for (i = 0; i < cfg->ncells; i++) { + cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL); + if (!cells[i]) { + rval = -ENOMEM; + goto err; + } + + rval = nvmem_cell_info_to_nvmem_cell(nvmem, &info[i], cells[i]); + if (IS_ERR_VALUE(rval)) { + kfree(cells[i]); + goto err; + } + + nvmem_cell_add(cells[i]); + } + + nvmem->ncells = cfg->ncells; + /* remove tmp array */ + kfree(cells); + + return 0; +err: + while (--i) + nvmem_cell_drop(cells[i]); + + return rval; +} + +/** + * nvmem_register() - Register a nvmem device for given nvmem_config. + * Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem + * + * @config: nvmem device configuration with which nvmem device is created. + * + * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device + * on success. + */ + +struct nvmem_device *nvmem_register(const struct nvmem_config *config) +{ + struct nvmem_device *nvmem; + struct device_node *np; + struct regmap *rm; + int rval; + + if (!config->dev) + return ERR_PTR(-EINVAL); + + rm = dev_get_regmap(config->dev, NULL); + if (!rm) { + dev_err(config->dev, "Regmap not found\n"); + return ERR_PTR(-EINVAL); + } + + nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return ERR_PTR(-ENOMEM); + + rval = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL); + if (rval < 0) { + kfree(nvmem); + return ERR_PTR(rval); + } + + nvmem->id = rval; + nvmem->regmap = rm; + nvmem->owner = config->owner; + nvmem->stride = regmap_get_reg_stride(rm); + nvmem->word_size = regmap_get_val_bytes(rm); + nvmem->size = regmap_get_max_register(rm) + nvmem->stride; + nvmem->dev.type = &nvmem_provider_type; + nvmem->dev.bus = &nvmem_bus_type; + nvmem->dev.parent = config->dev; + np = config->dev->of_node; + nvmem->dev.of_node = np; + dev_set_name(&nvmem->dev, "%s%d", + config->name ? : "nvmem", config->id); + + nvmem->read_only = of_property_read_bool(np, "read-only") | + config->read_only; + nvmem->bin = bin_attr_template; + nvmem->bin.attr.mode = nvmem->read_only ? S_IRUSR : (S_IRUSR | S_IWUSR); + nvmem->bin.attr.mode |= config->perm; + nvmem->bin.size = nvmem->size; + 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); + } + + rval = device_create_bin_file(&nvmem->dev, &nvmem->bin); + if (rval) + dev_err(&nvmem->dev, "Failed to create nvmem binary file\n"); + + if (config->cells) + nvmem_add_cells(nvmem, config); + + return nvmem; +} +EXPORT_SYMBOL_GPL(nvmem_register); + +/** + * nvmem_unregister() - Unregister previously registered nvmem device + * + * @nvmem: Pointer to previously registered nvmem device. + * + * Return: Will be an negative on error or a zero on success. + */ +int nvmem_unregister(struct nvmem_device *nvmem) +{ + mutex_lock(&nvmem_mutex); + if (nvmem->users) { + mutex_unlock(&nvmem_mutex); + return -EBUSY; + } + mutex_unlock(&nvmem_mutex); + + nvmem_device_remove_all_cells(nvmem); + device_remove_bin_file(&nvmem->dev, &nvmem->bin); + device_del(&nvmem->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_unregister); + +static struct nvmem_device *__nvmem_device_get(struct device_node *np, + struct nvmem_cell **cellp, + const char *cell_id) +{ + struct nvmem_device *nvmem = NULL; + + mutex_lock(&nvmem_mutex); + + if (np) { + nvmem = of_nvmem_find(np); + if (!nvmem) { + mutex_unlock(&nvmem_mutex); + return ERR_PTR(-EPROBE_DEFER); + } + } else { + struct nvmem_cell *cell = nvmem_find_cell(cell_id); + + if (cell) { + nvmem = cell->nvmem; + *cellp = cell; + } + + if (!nvmem) { + mutex_unlock(&nvmem_mutex); + return ERR_PTR(-ENOENT); + } + } + + nvmem->users++; + mutex_unlock(&nvmem_mutex); + + if (!try_module_get(nvmem->owner)) { + dev_err(&nvmem->dev, + "could not increase module refcount for cell %s\n", + nvmem->name); + + mutex_lock(&nvmem_mutex); + nvmem->users--; + mutex_unlock(&nvmem_mutex); + + return ERR_PTR(-EINVAL); + } + + return nvmem; +} + +static void __nvmem_device_put(struct nvmem_device *nvmem) +{ + module_put(nvmem->owner); + mutex_lock(&nvmem_mutex); + nvmem->users--; + mutex_unlock(&nvmem_mutex); +} + +static int nvmem_match(struct device *dev, void *data) +{ + return !strcmp(dev_name(dev), data); +} + +static struct nvmem_device *nvmem_find(const char *name) +{ + struct device *d; + + d = bus_find_device(&nvmem_bus_type, NULL, (void *)name, nvmem_match); + + if (!d) + return NULL; + + return to_nvmem_device(d); +} + +#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF) +/** + * of_nvmem_device_get() - Get nvmem device from a given id + * + * @dev node: Device tree node that uses the nvmem device + * @id: nvmem name from nvmem-names property. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. + */ +struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id) +{ + + struct device_node *nvmem_np; + int index; + + index = of_property_match_string(np, "nvmem-names", id); + + nvmem_np = of_parse_phandle(np, "nvmem", index); + if (!nvmem_np) + return ERR_PTR(-EINVAL); + + return __nvmem_device_get(nvmem_np, NULL, NULL); +} +EXPORT_SYMBOL_GPL(of_nvmem_device_get); +#endif + +/** + * nvmem_device_get() - Get nvmem device from a given id + * + * @dev : Device that uses the nvmem device + * @id: nvmem name from nvmem-names property. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. + */ +struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name) +{ + if (dev->of_node) { /* try dt first */ + struct nvmem_device *nvmem; + + nvmem = of_nvmem_device_get(dev->of_node, dev_name); + + if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER) + return nvmem; + + } + + return nvmem_find(dev_name); +} +EXPORT_SYMBOL_GPL(nvmem_device_get); + +static int devm_nvmem_device_match(struct device *dev, void *res, void *data) +{ + struct nvmem_device **nvmem = res; + + if (WARN_ON(!nvmem || !*nvmem)) + return 0; + + return *nvmem == data; +} + +static void devm_nvmem_device_release(struct device *dev, void *res) +{ + nvmem_device_put(*(struct nvmem_device **)res); +} + +/** + * devm_nvmem_device_put() - put alredy got nvmem device + * + * @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(), + * that needs to be released. + */ +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_device_release, + devm_nvmem_device_match, nvmem); + + WARN_ON(ret); +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_put); + +/** + * nvmem_device_put() - put alredy got nvmem device + * + * @nvmem: pointer to nvmem device that needs to be released. + */ +void nvmem_device_put(struct nvmem_device *nvmem) +{ + __nvmem_device_put(nvmem); +} +EXPORT_SYMBOL_GPL(nvmem_device_put); + +/** + * devm_nvmem_device_get() - Get nvmem cell of device form a given id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem name in nvmems property. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_cell + * on success. The nvmem_cell will be freed by the automatically once the + * device is freed. + */ +struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id) +{ + struct nvmem_device **ptr, *nvmem; + + ptr = devres_alloc(devm_nvmem_device_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + nvmem = nvmem_device_get(dev, id); + if (!IS_ERR(nvmem)) { + *ptr = nvmem; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return nvmem; +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_get); + +static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id) +{ + struct nvmem_cell *cell = NULL; + struct nvmem_device *nvmem; + + nvmem = __nvmem_device_get(NULL, &cell, cell_id); + if (IS_ERR(nvmem)) + return ERR_CAST(nvmem); + + return cell; +} + +#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF) +/** + * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem cell name from nvmem-cell-names property. + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, + const char *name) +{ + struct device_node *cell_np, *nvmem_np; + struct nvmem_cell *cell; + struct nvmem_device *nvmem; + const __be32 *addr; + int rval, len, index; + + index = of_property_match_string(np, "nvmem-cell-names", name); + + cell_np = of_parse_phandle(np, "nvmem-cells", index); + if (!cell_np) + return ERR_PTR(-EINVAL); + + nvmem_np = of_get_next_parent(cell_np); + if (!nvmem_np) + return ERR_PTR(-EINVAL); + + nvmem = __nvmem_device_get(nvmem_np, NULL, NULL); + if (IS_ERR(nvmem)) + return ERR_CAST(nvmem); + + addr = of_get_property(cell_np, "reg", &len); + if (!addr || (len < 2 * sizeof(u32))) { + dev_err(&nvmem->dev, "nvmem: invalid reg on %s\n", + cell_np->full_name); + rval = -EINVAL; + goto err_mem; + } + + cell = kzalloc(sizeof(*cell), GFP_KERNEL); + if (!cell) { + rval = -ENOMEM; + goto err_mem; + } + + cell->nvmem = nvmem; + cell->offset = be32_to_cpup(addr++); + cell->bytes = be32_to_cpup(addr); + cell->name = cell_np->name; + + addr = of_get_property(cell_np, "bits", &len); + if (addr && len == (2 * sizeof(u32))) { + cell->bit_offset = be32_to_cpup(addr++); + cell->nbits = be32_to_cpup(addr); + } + + if (cell->nbits) + cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, + BITS_PER_BYTE); + + if (!IS_ALIGNED(cell->offset, nvmem->stride)) { + dev_err(&nvmem->dev, + "cell %s unaligned to nvmem stride %d\n", + cell->name, nvmem->stride); + rval = -EINVAL; + goto err_sanity; + } + + nvmem_cell_add(cell); + + return cell; + +err_sanity: + kfree(cell); + +err_mem: + __nvmem_device_put(nvmem); + + return ERR_PTR(rval); +} +EXPORT_SYMBOL_GPL(of_nvmem_cell_get); +#endif + +/** + * nvmem_cell_get() - Get nvmem cell of device form a given cell name + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem cell name to get. + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *cell_id) +{ + struct nvmem_cell *cell; + + if (dev->of_node) { /* try dt first */ + cell = of_nvmem_cell_get(dev->of_node, cell_id); + if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER) + return cell; + } + + return nvmem_cell_get_from_list(cell_id); +} +EXPORT_SYMBOL_GPL(nvmem_cell_get); + +static void devm_nvmem_cell_release(struct device *dev, void *res) +{ + nvmem_cell_put(*(struct nvmem_cell **)res); +} + +/** + * devm_nvmem_cell_get() - Get nvmem cell of device form a given id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem id in nvmem-names property. + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * automatically once the device is freed. + */ +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id) +{ + struct nvmem_cell **ptr, *cell; + + ptr = devres_alloc(devm_nvmem_cell_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + cell = nvmem_cell_get(dev, id); + if (!IS_ERR(cell)) { + *ptr = cell; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return cell; +} +EXPORT_SYMBOL_GPL(devm_nvmem_cell_get); + +static int devm_nvmem_cell_match(struct device *dev, void *res, void *data) +{ + struct nvmem_cell **c = res; + + if (WARN_ON(!c || !*c)) + return 0; + + return *c == data; +} + +/** + * devm_nvmem_cell_put() - Release previously allocated nvmem cell + * from devm_nvmem_cell_get. + * + * @cell: Previously allocated nvmem cell by devm_nvmem_cell_get() + */ +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_cell_release, + devm_nvmem_cell_match, cell); + + WARN_ON(ret); +} +EXPORT_SYMBOL(devm_nvmem_cell_put); + +/** + * nvmem_cell_put() - Release previously allocated nvmem cell. + * + * @cell: Previously allocated nvmem cell by nvmem_cell_get() + */ +void nvmem_cell_put(struct nvmem_cell *cell) +{ + struct nvmem_device *nvmem = cell->nvmem; + + __nvmem_device_put(nvmem); + nvmem_cell_drop(cell); +} +EXPORT_SYMBOL_GPL(nvmem_cell_put); + +static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell, + void *buf) +{ + u8 *p, *b; + int i, bit_offset = cell->bit_offset; + + p = b = buf; + if (bit_offset) { + /* First shift */ + *b++ >>= bit_offset; + + /* setup rest of the bytes if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get bits from next byte and shift them towards msb */ + *p |= *b << (BITS_PER_BYTE - bit_offset); + + p = b; + *b++ >>= bit_offset; + } + + /* result fits in less bytes */ + if (cell->bytes != DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE)) + *p-- = 0; + } + /* clear msb bits if any leftover in the last byte */ + *p &= GENMASK((cell->nbits%BITS_PER_BYTE) - 1, 0); +} + +static int __nvmem_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell *cell, + void *buf, size_t *len) +{ + int rc; + + rc = regmap_raw_read(nvmem->regmap, cell->offset, buf, cell->bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + /* shift bits in-place */ + if (cell->bit_offset || cell->nbits) + nvmem_shift_read_buffer_in_place(cell, buf); + + *len = cell->bytes; + + return 0; +} + +/** + * nvmem_cell_read() - Read a given nvmem cell + * + * @cell: nvmem cell to be read. + * @len: pointer to length of cell which will be populated on successful read. + * + * Return: ERR_PTR() on error or a valid pointer to a char * buffer on success. + * The buffer should be freed by the consumer with a kfree(). + */ +void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len) +{ + struct nvmem_device *nvmem = cell->nvmem; + u8 *buf; + int rc; + + if (!nvmem || !nvmem->regmap) + return ERR_PTR(-EINVAL); + + buf = kzalloc(cell->bytes, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + rc = __nvmem_cell_read(nvmem, cell, buf, len); + if (IS_ERR_VALUE(rc)) { + kfree(buf); + return ERR_PTR(rc); + } + + return buf; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read); + +static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell, + u8 *_buf, int len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int i, rc, nbits, bit_offset = cell->bit_offset; + u8 v, *p, *buf, *b, pbyte, pbits; + + nbits = cell->nbits; + buf = kzalloc(cell->bytes, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + memcpy(buf, _buf, len); + p = b = buf; + + if (bit_offset) { + pbyte = *b; + *b <<= bit_offset; + + /* setup the first byte with lsb bits from nvmem */ + rc = regmap_raw_read(nvmem->regmap, cell->offset, &v, 1); + *b++ |= GENMASK(bit_offset - 1, 0) & v; + + /* setup rest of the byte if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get last byte bits and shift them towards lsb */ + pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset); + pbyte = *b; + p = b; + *b <<= bit_offset; + *b++ |= pbits; + } + } + + /* if it's not end on byte boundary */ + if ((nbits + bit_offset) % BITS_PER_BYTE) { + /* setup the last byte with msb bits from nvmem */ + rc = regmap_raw_read(nvmem->regmap, + cell->offset + cell->bytes - 1, &v, 1); + *p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v; + + } + + return buf; +} + +/** + * nvmem_cell_write() - Write to a given nvmem cell + * + * @cell: nvmem cell to be written. + * @buf: Buffer to be written. + * @len: length of buffer to be written to nvmem cell. + * + * Return: length of bytes written or negative on failure. + */ +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int rc; + + if (!nvmem || !nvmem->regmap || nvmem->read_only || + (cell->bit_offset == 0 && len != cell->bytes)) + return -EINVAL; + + if (cell->bit_offset || cell->nbits) { + buf = nvmem_cell_prepare_write_buffer(cell, buf, len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + } + + rc = regmap_raw_write(nvmem->regmap, cell->offset, buf, cell->bytes); + + /* free the tmp buffer */ + if (cell->bit_offset || cell->nbits) + kfree(buf); + + if (IS_ERR_VALUE(rc)) + return rc; + + return len; +} +EXPORT_SYMBOL_GPL(nvmem_cell_write); + +/** + * nvmem_device_cell_read() - Read a given nvmem device and cell + * + * @nvmem: nvmem device to read from. + * @info: nvmem cell info to be read. + * @buf: buffer pointer which will be populated on successful read. + * + * Return: length of successful bytes read on success and negative + * error code on error. + */ +ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell cell; + int rc; + ssize_t len; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell); + if (IS_ERR_VALUE(rc)) + return rc; + + rc = __nvmem_cell_read(nvmem, &cell, buf, &len); + if (IS_ERR_VALUE(rc)) + return rc; + + return len; +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_read); + +/** + * nvmem_device_cell_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @info: nvmem cell info to be written + * @buf: buffer to be written to cell. + * + * Return: length of bytes written or negative error code on failure. + * */ +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell cell; + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell); + if (IS_ERR_VALUE(rc)) + return rc; + + return nvmem_cell_write(&cell, buf, cell.bytes); +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_write); + +/** + * nvmem_device_read() - Read from a given nvmem device + * + * @nvmem: nvmem device to read from. + * @offset: offset in nvmem device. + * @bytes: number of bytes to read. + * @buf: buffer pointer which will be populated on successful read. + * + * Return: length of successful bytes read on success and negative + * error code on error. + */ +int nvmem_device_read(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = regmap_raw_read(nvmem->regmap, offset, buf, bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_read); + +/** + * nvmem_device_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @offset: offset in nvmem device. + * @bytes: number of bytes to write. + * @buf: buffer to be written. + * + * Return: length of bytes written or negative error code on failure. + * */ +int nvmem_device_write(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = regmap_raw_write(nvmem->regmap, offset, buf, bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_write); + +static int __init nvmem_init(void) +{ + return bus_register(&nvmem_bus_type); +} + +static void __exit nvmem_exit(void) +{ + bus_unregister(&nvmem_bus_type); +} + +subsys_initcall(nvmem_init); +module_exit(nvmem_exit); + +MODULE_AUTHOR("Srinivas Kandagatla + * + * Based on the barebox ocotp driver, + * Copyright (c) 2010 Baruch Siach , + * Orex Computed Radiography + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ocotp_priv { + struct device *dev; + void __iomem *base; + unsigned int nregs; +}; + +static int imx_ocotp_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct ocotp_priv *priv = context; + unsigned int offset = *(u32 *)reg; + unsigned int count; + int i; + u32 index; + + index = offset >> 2; + count = val_size >> 2; + + if (count > (priv->nregs - index)) + count = priv->nregs - index; + + for (i = index; i < (index + count); i++) { + *(u32 *)val = readl(priv->base + 0x400 + i * 0x10); + val += 4; + } + + return (i - index) * 4; +} + +static int imx_ocotp_write(void *context, const void *data, size_t count) +{ + /* Not implemented */ + return 0; +} + +static struct regmap_bus imx_ocotp_bus = { + .read = imx_ocotp_read, + .write = imx_ocotp_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static bool imx_ocotp_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static struct regmap_config imx_ocotp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .writeable_reg = imx_ocotp_writeable_reg, + .name = "imx-ocotp", +}; + +static struct nvmem_config imx_ocotp_nvmem_config = { + .name = "imx-ocotp", + .read_only = true, + .owner = THIS_MODULE, +}; + +static const struct of_device_id imx_ocotp_dt_ids[] = { + { .compatible = "fsl,imx6q-ocotp", (void *)128 }, + { .compatible = "fsl,imx6sl-ocotp", (void *)32 }, + { .compatible = "fsl,imx6sx-ocotp", (void *)128 }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); + +static int imx_ocotp_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct resource *res; + struct regmap *regmap; + struct ocotp_priv *priv; + struct nvmem_device *nvmem; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + of_id = of_match_device(imx_ocotp_dt_ids, dev); + priv->nregs = (unsigned int)of_id->data; + imx_ocotp_regmap_config.max_register = 4 * priv->nregs - 4; + + regmap = devm_regmap_init(dev, &imx_ocotp_bus, priv, + &imx_ocotp_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + imx_ocotp_nvmem_config.dev = dev; + nvmem = nvmem_register(&imx_ocotp_nvmem_config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int imx_ocotp_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static struct platform_driver imx_ocotp_driver = { + .probe = imx_ocotp_probe, + .remove = imx_ocotp_remove, + .driver = { + .name = "imx_ocotp", + .of_match_table = imx_ocotp_dt_ids, + }, +}; +module_platform_driver(imx_ocotp_driver); + +MODULE_AUTHOR("Philipp Zabel "); +MODULE_DESCRIPTION("i.MX6 OCOTP fuse box driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/mxs-ocotp.c b/drivers/nvmem/mxs-ocotp.c new file mode 100644 index 0000000000000..8ba19bba31569 --- /dev/null +++ b/drivers/nvmem/mxs-ocotp.c @@ -0,0 +1,257 @@ +/* + * Freescale MXS On-Chip OTP driver + * + * Copyright (C) 2015 Stefan Wahren + * + * Based on the driver from Huang Shijie and Christoph G. Baumann + * + * 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 + +/* OCOTP registers and bits */ + +#define BM_OCOTP_CTRL_RD_BANK_OPEN BIT(12) +#define BM_OCOTP_CTRL_ERROR BIT(9) +#define BM_OCOTP_CTRL_BUSY BIT(8) + +#define OCOTP_TIMEOUT 10000 +#define OCOTP_DATA_OFFSET 0x20 + +struct mxs_ocotp { + struct clk *clk; + void __iomem *base; + struct nvmem_device *nvmem; +}; + +static int mxs_ocotp_wait(struct mxs_ocotp *otp) +{ + int timeout = OCOTP_TIMEOUT; + unsigned int status = 0; + + while (timeout--) { + status = readl(otp->base); + + if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR))) + break; + + cpu_relax(); + } + + if (status & BM_OCOTP_CTRL_BUSY) + return -EBUSY; + else if (status & BM_OCOTP_CTRL_ERROR) + return -EIO; + + return 0; +} + +static int mxs_ocotp_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct mxs_ocotp *otp = context; + unsigned int offset = *(u32 *)reg; + u32 *buf = val; + int ret; + + ret = clk_enable(otp->clk); + if (ret) + return ret; + + writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR); + + ret = mxs_ocotp_wait(otp); + if (ret) + goto disable_clk; + + /* open OCOTP banks for read */ + writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET); + + /* approximately wait 33 hclk cycles */ + udelay(1); + + ret = mxs_ocotp_wait(otp); + if (ret) + goto close_banks; + + while (val_size) { + if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) { + /* fill up non-data register */ + *buf = 0; + } else { + *buf = readl(otp->base + offset); + } + + buf++; + val_size--; + offset += reg_size; + } + +close_banks: + /* close banks for power saving */ + writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR); + +disable_clk: + clk_disable(otp->clk); + + return ret; +} + +static int mxs_ocotp_write(void *context, const void *data, size_t count) +{ + /* We don't want to support writing */ + return 0; +} + +static bool mxs_ocotp_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static struct nvmem_config ocotp_config = { + .name = "mxs-ocotp", + .owner = THIS_MODULE, +}; + +static const struct regmap_range imx23_ranges[] = { + regmap_reg_range(OCOTP_DATA_OFFSET, 0x210), +}; + +static const struct regmap_access_table imx23_access = { + .yes_ranges = imx23_ranges, + .n_yes_ranges = ARRAY_SIZE(imx23_ranges), +}; + +static const struct regmap_range imx28_ranges[] = { + regmap_reg_range(OCOTP_DATA_OFFSET, 0x290), +}; + +static const struct regmap_access_table imx28_access = { + .yes_ranges = imx28_ranges, + .n_yes_ranges = ARRAY_SIZE(imx28_ranges), +}; + +static struct regmap_bus mxs_ocotp_bus = { + .read = mxs_ocotp_read, + .write = mxs_ocotp_write, /* make regmap_init() happy */ + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct regmap_config mxs_ocotp_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 16, + .writeable_reg = mxs_ocotp_writeable_reg, +}; + +static const struct of_device_id mxs_ocotp_match[] = { + { .compatible = "fsl,imx23-ocotp", .data = &imx23_access }, + { .compatible = "fsl,imx28-ocotp", .data = &imx28_access }, + { /* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, mxs_ocotp_match); + +static int mxs_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxs_ocotp *otp; + struct resource *res; + const struct of_device_id *match; + struct regmap *regmap; + const struct regmap_access_table *access; + int ret; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return -EINVAL; + + otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + otp->base = devm_ioremap_resource(dev, res); + if (IS_ERR(otp->base)) + return PTR_ERR(otp->base); + + otp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(otp->clk)) + return PTR_ERR(otp->clk); + + ret = clk_prepare(otp->clk); + if (ret < 0) { + dev_err(dev, "failed to prepare clk: %d\n", ret); + return ret; + } + + access = match->data; + mxs_ocotp_config.rd_table = access; + mxs_ocotp_config.max_register = access->yes_ranges[0].range_max; + + regmap = devm_regmap_init(dev, &mxs_ocotp_bus, otp, &mxs_ocotp_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + ret = PTR_ERR(regmap); + goto err_clk; + } + + ocotp_config.dev = dev; + otp->nvmem = nvmem_register(&ocotp_config); + if (IS_ERR(otp->nvmem)) { + ret = PTR_ERR(otp->nvmem); + goto err_clk; + } + + platform_set_drvdata(pdev, otp); + + return 0; + +err_clk: + clk_unprepare(otp->clk); + + return ret; +} + +static int mxs_ocotp_remove(struct platform_device *pdev) +{ + struct mxs_ocotp *otp = platform_get_drvdata(pdev); + + clk_unprepare(otp->clk); + + return nvmem_unregister(otp->nvmem); +} + +static struct platform_driver mxs_ocotp_driver = { + .probe = mxs_ocotp_probe, + .remove = mxs_ocotp_remove, + .driver = { + .name = "mxs-ocotp", + .of_match_table = mxs_ocotp_match, + }, +}; + +module_platform_driver(mxs_ocotp_driver); +MODULE_AUTHOR("Stefan Wahren "); +MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c new file mode 100644 index 0000000000000..afb67e7eeee4a --- /dev/null +++ b/drivers/nvmem/qfprom.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 Srinivas Kandagatla + * + * 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. + */ + +#include +#include +#include +#include +#include + +static struct regmap_config qfprom_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, +}; + +static struct nvmem_config econfig = { + .name = "qfprom", + .owner = THIS_MODULE, +}; + +static int qfprom_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static int qfprom_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct regmap *regmap; + void __iomem *base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + qfprom_regmap_config.max_register = resource_size(res) - 1; + + regmap = devm_regmap_init_mmio(dev, base, &qfprom_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + econfig.dev = dev; + nvmem = nvmem_register(&econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static const struct of_device_id qfprom_of_match[] = { + { .compatible = "qcom,qfprom",}, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, qfprom_of_match); + +static struct platform_driver qfprom_driver = { + .probe = qfprom_probe, + .remove = qfprom_remove, + .driver = { + .name = "qcom,qfprom", + .of_match_table = qfprom_of_match, + }, +}; +module_platform_driver(qfprom_driver); +MODULE_AUTHOR("Srinivas Kandagatla "); +MODULE_DESCRIPTION("Qualcomm QFPROM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/rockchip-efuse.c b/drivers/nvmem/rockchip-efuse.c new file mode 100644 index 0000000000000..f552134242223 --- /dev/null +++ b/drivers/nvmem/rockchip-efuse.c @@ -0,0 +1,186 @@ +/* + * Rockchip eFuse Driver + * + * Copyright (c) 2015 Rockchip Electronics Co. Ltd. + * Author: Caesar Wang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License 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 EFUSE_A_SHIFT 6 +#define EFUSE_A_MASK 0x3ff +#define EFUSE_PGENB BIT(3) +#define EFUSE_LOAD BIT(2) +#define EFUSE_STROBE BIT(1) +#define EFUSE_CSB BIT(0) + +#define REG_EFUSE_CTRL 0x0000 +#define REG_EFUSE_DOUT 0x0004 + +struct rockchip_efuse_context { + struct device *dev; + void __iomem *base; + struct clk *efuse_clk; +}; + +static int rockchip_efuse_write(void *context, const void *data, size_t count) +{ + /* Nothing TBD, Read-Only */ + return 0; +} + +static int rockchip_efuse_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + unsigned int offset = *(u32 *)reg; + struct rockchip_efuse_context *_context = context; + void __iomem *base = _context->base; + struct clk *clk = _context->efuse_clk; + u8 *buf = val; + int ret; + + ret = clk_prepare_enable(clk); + if (ret < 0) { + dev_err(_context->dev, "failed to prepare/enable efuse clk\n"); + return ret; + } + + writel(EFUSE_LOAD | EFUSE_PGENB, base + REG_EFUSE_CTRL); + udelay(1); + while (val_size) { + writel(readl(base + REG_EFUSE_CTRL) & + (~(EFUSE_A_MASK << EFUSE_A_SHIFT)), + base + REG_EFUSE_CTRL); + writel(readl(base + REG_EFUSE_CTRL) | + ((offset & EFUSE_A_MASK) << EFUSE_A_SHIFT), + base + REG_EFUSE_CTRL); + udelay(1); + writel(readl(base + REG_EFUSE_CTRL) | + EFUSE_STROBE, base + REG_EFUSE_CTRL); + udelay(1); + *buf++ = readb(base + REG_EFUSE_DOUT); + writel(readl(base + REG_EFUSE_CTRL) & + (~EFUSE_STROBE), base + REG_EFUSE_CTRL); + udelay(1); + + val_size -= 1; + offset += 1; + } + + /* Switch to standby mode */ + writel(EFUSE_PGENB | EFUSE_CSB, base + REG_EFUSE_CTRL); + + clk_disable_unprepare(clk); + + return 0; +} + +static struct regmap_bus rockchip_efuse_bus = { + .read = rockchip_efuse_read, + .write = rockchip_efuse_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct regmap_config rockchip_efuse_regmap_config = { + .reg_bits = 32, + .reg_stride = 1, + .val_bits = 8, +}; + +static struct nvmem_config econfig = { + .name = "rockchip-efuse", + .owner = THIS_MODULE, + .read_only = true, +}; + +static const struct of_device_id rockchip_efuse_match[] = { + { .compatible = "rockchip,rockchip-efuse",}, + { /* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, rockchip_efuse_match); + +static int rockchip_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct regmap *regmap; + void __iomem *base; + struct clk *clk; + struct rockchip_efuse_context *context; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + context = devm_kzalloc(dev, sizeof(struct rockchip_efuse_context), + GFP_KERNEL); + if (IS_ERR(context)) + return PTR_ERR(context); + + clk = devm_clk_get(dev, "pclk_efuse"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + context->dev = dev; + context->base = base; + context->efuse_clk = clk; + + rockchip_efuse_regmap_config.max_register = resource_size(res) - 1; + + regmap = devm_regmap_init(dev, &rockchip_efuse_bus, + context, &rockchip_efuse_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + econfig.dev = dev; + nvmem = nvmem_register(&econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int rockchip_efuse_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static struct platform_driver rockchip_efuse_driver = { + .probe = rockchip_efuse_probe, + .remove = rockchip_efuse_remove, + .driver = { + .name = "rockchip-efuse", + .of_match_table = rockchip_efuse_match, + }, +}; + +module_platform_driver(rockchip_efuse_driver); +MODULE_DESCRIPTION("rockchip_efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c new file mode 100644 index 0000000000000..cfa3b85064dd2 --- /dev/null +++ b/drivers/nvmem/sunxi_sid.c @@ -0,0 +1,180 @@ +/* + * Allwinner sunXi SoCs Security ID support. + * + * Copyright (c) 2013 Oliver Schinagl + * Copyright (C) 2014 Maxime Ripard + * + * 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 + + +static struct nvmem_config econfig = { + .name = "sunxi-sid", + .read_only = true, + .owner = THIS_MODULE, +}; + +struct sunxi_sid { + void __iomem *base; +}; + +/* We read the entire key, due to a 32 bit read alignment requirement. Since we + * want to return the requested byte, this results in somewhat slower code and + * uses 4 times more reads as needed but keeps code simpler. Since the SID is + * only very rarely probed, this is not really an issue. + */ +static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, + const unsigned int offset) +{ + u32 sid_key; + + sid_key = ioread32be(sid->base + round_down(offset, 4)); + sid_key >>= (offset % 4) * 8; + + return sid_key; /* Only return the last byte */ +} + +static int sunxi_sid_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct sunxi_sid *sid = context; + unsigned int offset = *(u32 *)reg; + u8 *buf = val; + + while (val_size) { + *buf++ = sunxi_sid_read_byte(sid, offset); + val_size--; + offset++; + } + + return 0; +} + +static int sunxi_sid_write(void *context, const void *data, size_t count) +{ + /* Unimplemented, dummy to keep regmap core happy */ + return 0; +} + +static struct regmap_bus sunxi_sid_bus = { + .read = sunxi_sid_read, + .write = sunxi_sid_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static bool sunxi_sid_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static struct regmap_config sunxi_sid_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, + .writeable_reg = sunxi_sid_writeable_reg, +}; + +static int sunxi_sid_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct regmap *regmap; + struct sunxi_sid *sid; + int ret, i, size; + char *randomness; + + sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); + if (!sid) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sid->base = devm_ioremap_resource(dev, res); + if (IS_ERR(sid->base)) + return PTR_ERR(sid->base); + + size = resource_size(res) - 1; + sunxi_sid_regmap_config.max_register = size; + + regmap = devm_regmap_init(dev, &sunxi_sid_bus, sid, + &sunxi_sid_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + + econfig.dev = dev; + nvmem = nvmem_register(&econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + randomness = kzalloc(sizeof(u8) * size, GFP_KERNEL); + if (!randomness) { + ret = -EINVAL; + goto err_unreg_nvmem; + } + + for (i = 0; i < size; i++) + randomness[i] = sunxi_sid_read_byte(sid, i); + + add_device_randomness(randomness, size); + kfree(randomness); + + platform_set_drvdata(pdev, nvmem); + + return 0; + +err_unreg_nvmem: + nvmem_unregister(nvmem); + return ret; +} + +static int sunxi_sid_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static const struct of_device_id sunxi_sid_of_match[] = { + { .compatible = "allwinner,sun4i-a10-sid" }, + { .compatible = "allwinner,sun7i-a20-sid" }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); + +static struct platform_driver sunxi_sid_driver = { + .probe = sunxi_sid_probe, + .remove = sunxi_sid_remove, + .driver = { + .name = "eeprom-sunxi-sid", + .of_match_table = sunxi_sid_of_match, + }, +}; +module_platform_driver(sunxi_sid_driver); + +MODULE_AUTHOR("Oliver Schinagl "); +MODULE_DESCRIPTION("Allwinner sunxi security id driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/vf610-ocotp.c b/drivers/nvmem/vf610-ocotp.c new file mode 100644 index 0000000000000..8641319efeda7 --- /dev/null +++ b/drivers/nvmem/vf610-ocotp.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2015 Toradex AG. + * + * Author: Sanchayan Maity + * + * Based on the barebox ocotp driver, + * Copyright (c) 2010 Baruch Siach + * Orex Computed Radiography + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* OCOTP Register Offsets */ +#define OCOTP_CTRL_REG 0x00 +#define OCOTP_CTRL_SET 0x04 +#define OCOTP_CTRL_CLR 0x08 +#define OCOTP_TIMING 0x10 +#define OCOTP_DATA 0x20 +#define OCOTP_READ_CTRL_REG 0x30 +#define OCOTP_READ_FUSE_DATA 0x40 + +/* OCOTP Register bits and masks */ +#define OCOTP_CTRL_WR_UNLOCK 16 +#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77 +#define OCOTP_CTRL_WR_UNLOCK_MASK GENMASK(31, 16) +#define OCOTP_CTRL_ADDR 0 +#define OCOTP_CTRL_ADDR_MASK GENMASK(6, 0) +#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10) +#define OCOTP_CTRL_ERR BIT(9) +#define OCOTP_CTRL_BUSY BIT(8) + +#define OCOTP_TIMING_STROBE_READ 16 +#define OCOTP_TIMING_STROBE_READ_MASK GENMASK(21, 16) +#define OCOTP_TIMING_RELAX 12 +#define OCOTP_TIMING_RELAX_MASK GENMASK(15, 12) +#define OCOTP_TIMING_STROBE_PROG 0 +#define OCOTP_TIMING_STROBE_PROG_MASK GENMASK(11, 0) + +#define OCOTP_READ_CTRL_READ_FUSE 0x1 + +#define VF610_OCOTP_TIMEOUT 100000 + +#define BF(value, field) (((value) << field) & field##_MASK) + +#define DEF_RELAX 20 + +static const int base_to_fuse_addr_mappings[][2] = { + {0x400, 0x00}, + {0x410, 0x01}, + {0x420, 0x02}, + {0x450, 0x05}, + {0x4F0, 0x0F}, + {0x600, 0x20}, + {0x610, 0x21}, + {0x620, 0x22}, + {0x630, 0x23}, + {0x640, 0x24}, + {0x650, 0x25}, + {0x660, 0x26}, + {0x670, 0x27}, + {0x6F0, 0x2F}, + {0x880, 0x38}, + {0x890, 0x39}, + {0x8A0, 0x3A}, + {0x8B0, 0x3B}, + {0x8C0, 0x3C}, + {0x8D0, 0x3D}, + {0x8E0, 0x3E}, + {0x8F0, 0x3F}, + {0xC80, 0x78}, + {0xC90, 0x79}, + {0xCA0, 0x7A}, + {0xCB0, 0x7B}, + {0xCC0, 0x7C}, + {0xCD0, 0x7D}, + {0xCE0, 0x7E}, + {0xCF0, 0x7F}, +}; + +struct vf610_ocotp { + void __iomem *base; + struct clk *clk; + struct device *dev; + struct nvmem_device *nvmem; + int timing; +}; + +static int vf610_ocotp_wait_busy(void __iomem *base) +{ + int timeout = VF610_OCOTP_TIMEOUT; + + while ((readl(base) & OCOTP_CTRL_BUSY) && --timeout) + udelay(10); + + if (!timeout) { + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + return -ETIMEDOUT; + } + + udelay(10); + + return 0; +} + +static int vf610_ocotp_calculate_timing(struct vf610_ocotp *ocotp_dev) +{ + u32 clk_rate; + u32 relax, strobe_read, strobe_prog; + u32 timing; + + clk_rate = clk_get_rate(ocotp_dev->clk); + + /* Refer section OTP read/write timing parameters in TRM */ + relax = clk_rate / (1000000000 / DEF_RELAX) - 1; + strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; + strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; + + timing = BF(relax, OCOTP_TIMING_RELAX); + timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ); + timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG); + + return timing; +} + +static int vf610_get_fuse_address(int base_addr_offset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(base_to_fuse_addr_mappings); i++) { + if (base_to_fuse_addr_mappings[i][0] == base_addr_offset) + return base_to_fuse_addr_mappings[i][1]; + } + + return -EINVAL; +} + +static int vf610_ocotp_write(void *context, const void *data, size_t count) +{ + return 0; +} + +static int vf610_ocotp_read(void *context, + const void *off, size_t reg_size, + void *val, size_t val_size) +{ + struct vf610_ocotp *ocotp = context; + void __iomem *base = ocotp->base; + unsigned int offset = *(u32 *)off; + u32 reg, *buf = val; + int fuse_addr; + int ret; + + while (val_size > 0) { + fuse_addr = vf610_get_fuse_address(offset); + if (fuse_addr > 0) { + writel(ocotp->timing, base + OCOTP_TIMING); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + reg = readl(base + OCOTP_CTRL_REG); + reg &= ~OCOTP_CTRL_ADDR_MASK; + reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK; + reg |= BF(fuse_addr, OCOTP_CTRL_ADDR); + writel(reg, base + OCOTP_CTRL_REG); + + writel(OCOTP_READ_CTRL_READ_FUSE, + base + OCOTP_READ_CTRL_REG); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + if (readl(base) & OCOTP_CTRL_ERR) { + dev_dbg(ocotp->dev, "Error reading from fuse address %x\n", + fuse_addr); + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + } + + /* + * In case of error, we do not abort and expect to read + * 0xBADABADA as mentioned by the TRM. We just read this + * value and return. + */ + *buf = readl(base + OCOTP_READ_FUSE_DATA); + } else { + *buf = 0; + } + + buf++; + val_size--; + offset += reg_size; + } + + return 0; +} + +static struct regmap_bus vf610_ocotp_bus = { + .read = vf610_ocotp_read, + .write = vf610_ocotp_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct regmap_config ocotp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static struct nvmem_config ocotp_config = { + .name = "ocotp", + .owner = THIS_MODULE, +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "fsl,vf610-ocotp", }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, ocotp_of_match); + +static int vf610_ocotp_remove(struct platform_device *pdev) +{ + struct vf610_ocotp *ocotp_dev = platform_get_drvdata(pdev); + + return nvmem_unregister(ocotp_dev->nvmem); +} + +static int vf610_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct regmap *regmap; + struct vf610_ocotp *ocotp_dev; + + ocotp_dev = devm_kzalloc(&pdev->dev, + sizeof(struct vf610_ocotp), GFP_KERNEL); + if (!ocotp_dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ocotp_dev->base = devm_ioremap_resource(dev, res); + if (IS_ERR(ocotp_dev->base)) + return PTR_ERR(ocotp_dev->base); + + ocotp_dev->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ocotp_dev->clk)) { + dev_err(dev, "failed getting clock, err = %ld\n", + PTR_ERR(ocotp_dev->clk)); + return PTR_ERR(ocotp_dev->clk); + } + + ocotp_regmap_config.max_register = resource_size(res); + regmap = devm_regmap_init(dev, + &vf610_ocotp_bus, ocotp_dev, &ocotp_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + ocotp_config.dev = dev; + + ocotp_dev->nvmem = nvmem_register(&ocotp_config); + if (IS_ERR(ocotp_dev->nvmem)) + return PTR_ERR(ocotp_dev->nvmem); + + ocotp_dev->dev = dev; + platform_set_drvdata(pdev, ocotp_dev); + + ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); + + return 0; +} + +static struct platform_driver vf610_ocotp_driver = { + .probe = vf610_ocotp_probe, + .remove = vf610_ocotp_remove, + .driver = { + .name = "vf610-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(vf610_ocotp_driver); +MODULE_AUTHOR("Sanchayan Maity "); +MODULE_DESCRIPTION("Vybrid OCOTP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index 07bb3c8f191be..40f2dde8882bb 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -97,4 +97,12 @@ 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 + select OF_OVERLAY + select OF_FLATTREE + help + Enable a simple user-space driven DT overlay interface. + endmenu # OF diff --git a/drivers/of/Makefile b/drivers/of/Makefile index fcacb186a67be..b5df4a2258db2 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 5ed97246c2e7f..91e93bad1b612 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -192,6 +192,7 @@ int __of_attach_node_sysfs(struct device_node *np) void __init of_core_init(void) { struct device_node *np; + int ret; /* Create the kset, and register existing nodes */ mutex_lock(&of_mutex); @@ -208,6 +209,12 @@ void __init of_core_init(void) /* 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"); + + return 0; } static struct property *__of_find_property(const struct device_node *np, diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c new file mode 100644 index 0000000000000..9fb1b35825c8f --- /dev/null +++ b/drivers/of/configfs.c @@ -0,0 +1,333 @@ +/* + * 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; +} + +CONFIGFS_ATTR_STRUCT(cfs_overlay_item); +#define CFS_OVERLAY_ITEM_ATTR(_name, _mode, _show, _store) \ +struct cfs_overlay_item_attribute cfs_overlay_item_attr_##_name = \ + __CONFIGFS_ATTR(_name, _mode, _show, _store) +#define CFS_OVERLAY_ITEM_ATTR_RO(_name, _show) \ +struct cfs_overlay_item_attribute cfs_overlay_item_attr_##_name = \ + __CONFIGFS_ATTR_RO(_name, _show) + +CONFIGFS_BIN_ATTR_STRUCT(cfs_overlay_item); +#define CFS_OVERLAY_ITEM_BIN_ATTR(_name, _mode, _read, _write, _priv, _max) \ +struct cfs_overlay_item_bin_attribute cfs_overlay_item_bin_attr_##_name = \ + __CONFIGFS_BIN_ATTR(_name, _mode, _read, _write, _priv, _max) +#define CFS_OVERLAY_ITEM_BIN_ATTR_RO(_name, _read, _priv, _max) \ +struct cfs_overlay_item_bin_attribute cfs_overlay_item_bin_attr_##_name = \ + __CONFIGFS_BIN_ATTR_RO(_name, _read, _priv, _max) + +static ssize_t cfs_overlay_item_path_show(struct cfs_overlay_item *overlay, + char *page) +{ + return sprintf(page, "%s\n", overlay->path); +} + +static ssize_t cfs_overlay_item_path_store(struct cfs_overlay_item *overlay, + const char *page, size_t count) +{ + 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 cfs_overlay_item *overlay, + char *page) +{ + return sprintf(page, "%s\n", + overlay->ov_id >= 0 ? "applied" : "unapplied"); +} + +CFS_OVERLAY_ITEM_ATTR(path, S_IRUGO | S_IWUSR, + cfs_overlay_item_path_show, cfs_overlay_item_path_store); +CFS_OVERLAY_ITEM_ATTR_RO(status, cfs_overlay_item_status_show); + +static struct configfs_attribute *cfs_overlay_attrs[] = { + &cfs_overlay_item_attr_path.attr, + &cfs_overlay_item_attr_status.attr, + NULL, +}; + +ssize_t cfs_overlay_item_dtbo_read(struct cfs_overlay_item *overlay, + void *buf, size_t max_count) +{ + 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 cfs_overlay_item *overlay, + const void *buf, size_t count) +{ + 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; +} + +CFS_OVERLAY_ITEM_BIN_ATTR(dtbo, S_IRUGO | S_IWUSR, + cfs_overlay_item_dtbo_read, cfs_overlay_item_dtbo_write, + NULL, SZ_1M); + +static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { + &cfs_overlay_item_bin_attr_dtbo.bin_attr, + 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); +} + +CONFIGFS_ATTR_OPS(cfs_overlay_item); +CONFIGFS_BIN_ATTR_OPS(cfs_overlay_item); +static struct configfs_item_operations cfs_overlay_item_ops = { + .release = cfs_overlay_release, + .show_attribute = cfs_overlay_item_attr_show, + .store_attribute = cfs_overlay_item_attr_store, + .read_bin_attribute = cfs_overlay_item_bin_attr_read, + .write_bin_attribute = cfs_overlay_item_bin_attr_write, +}; + +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 53826b84e0ec6..afa8e31b08773 100644 --- a/drivers/of/dynamic.c +++ b/drivers/of/dynamic.c @@ -394,8 +394,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 +404,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 +444,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); @@ -779,3 +796,254 @@ int of_changeset_action(struct of_changeset *ocs, unsigned long action, list_add_tail(&ce->node, &ocs->entries); return 0; } + +/* 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; + + 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, length, i, ret; + char *value, *s; + + total = 0; + for (i = 0; i < count; i++) { + length = strlen(strs[i]); + total += length + 1; + } + + value = kmalloc(total, GFP_KERNEL); + if (!value) + return -ENOMEM; + + for (i = 0, s = value; i < count; i++) { + length = strlen(strs[i]); + memcpy(s, strs[i], length + 1); + s += length + 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/of_private.h b/drivers/of/of_private.h index 8e882e706cd8c..120eb44a7f774 100644 --- a/drivers/of/of_private.h +++ b/drivers/of/of_private.h @@ -90,4 +90,13 @@ extern void __of_detach_node_sysfs(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 + #endif /* _LINUX_OF_PRIVATE_H */ diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c index dee9270ba5471..f9b55d1bb6d79 100644 --- a/drivers/of/overlay.c +++ b/drivers/of/overlay.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "of_private.h" @@ -51,10 +53,17 @@ struct of_overlay { int count; struct of_overlay_info *ovinfo_tab; 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 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) @@ -184,33 +193,139 @@ 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; + } - pr_err("%s: Failed to find target for node %p (%s)\n", __func__, - info_node, info_node->name); + /* 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); + + 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; } /** @@ -234,7 +349,7 @@ 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; @@ -325,31 +440,140 @@ 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 an 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 ssize_t targets_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct of_overlay *ov = kobj_to_overlay(kobj); + struct of_overlay_info *ovinfo; + char *s, *e; + ssize_t ret; + int i, len; + + s = buf; + e = buf + PAGE_SIZE; + + mutex_lock(&of_mutex); + + /* targets */ + for (i = 0; i < ov->count; i++) { + ovinfo = &ov->ovinfo_tab[i]; + + len = snprintf(s, e - s, "%s\n", + of_node_full_name(ovinfo->target)); + if (len == 0) { + ret = -ENOSPC; + goto err; + } + s += len; + } + + /* the buffer is zero terminated */ + ret = s - buf; +err: + mutex_unlock(&of_mutex); + return ret; +} + +static struct kobj_attribute can_remove_attr = __ATTR_RO(can_remove); +static struct kobj_attribute targets_attr = __ATTR_RO(targets); + +static struct attribute *overlay_attrs[] = { + &can_remove_attr.attr, + &targets_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); @@ -385,6 +609,14 @@ int of_overlay_create(struct device_node *tree) 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; + } + /* add to the tail of the overlay list */ list_add_tail(&ov->node, &ov_list); @@ -392,6 +624,8 @@ int of_overlay_create(struct device_node *tree) return id; +err_cancel_overlay: + of_changeset_revert(&ov->cset); err_revert_overlay: err_abort_trans: of_free_overlay_info(ov); @@ -399,13 +633,68 @@ int of_overlay_create(struct device_node *tree) 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) @@ -512,7 +801,9 @@ int of_overlay_destroy(int id) of_free_overlay_info(ov); idr_remove(&ov_idr, id); of_changeset_destroy(&ov->cset); - kfree(ov); + + kobject_del(&ov->kobj); + kobject_put(&ov->kobj); err = 0; @@ -542,7 +833,8 @@ int of_overlay_destroy_all(void) of_changeset_revert(&ov->cset); of_free_overlay_info(ov); idr_remove(&ov_idr, ov->id); - kfree(ov); + kobject_del(&ov->kobj); + kobject_put(&ov->kobj); } mutex_unlock(&of_mutex); @@ -550,3 +842,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 12f7c3d649c88..8052b96c1d64d 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 02ba56c20fe12..e10ff5a9b9345 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 18016341d5a91..dcf0d76cd950a 100644 --- a/drivers/of/unittest.c +++ b/drivers/of/unittest.c @@ -1852,6 +1852,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; @@ -1903,6 +2169,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; diff --git a/drivers/remoteproc/pru_rproc.c b/drivers/remoteproc/pru_rproc.c index f12f1a99ba43f..c65e0e9e921fa 100644 --- a/drivers/remoteproc/pru_rproc.c +++ b/drivers/remoteproc/pru_rproc.c @@ -242,6 +242,14 @@ static void *pru_i_da_to_va(struct pru_rproc *pru, u32 da, int len) u32 offset; void *va = NULL; + /* GNU binutils do not support multiple address spaces. The + * default linker script from the unofficial GNU pru-ld places + * IRAM at an arbitrary high offset, in order to differentiate it + * from DRAM. Hence we need to strip the artificial offset + * from the IRAM address. + */ + da &= ~0xf0000000u; + if (len <= 0) return NULL; diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 8fab566e0f0bc..fe265a86074c2 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -725,11 +725,11 @@ static int spidev_probe(struct spi_device *spi) * compatbile 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 6cf0c58f538bc..6f5e82464d786 100644 --- a/drivers/staging/fbtft/Kconfig +++ b/drivers/staging/fbtft/Kconfig @@ -1,6 +1,7 @@ menuconfig FB_TFT tristate "Support for small TFT LCD display modules" - depends on FB && SPI && GPIOLIB + depends on FB && SPI + depends on GPIOLIB || COMPILE_TEST select FB_SYS_FILLRECT select FB_SYS_COPYAREA select FB_SYS_IMAGEBLIT @@ -12,7 +13,7 @@ config FB_TFT_AGM1264K_FL tristate "FB driver for the AGM1264K-FL LCD display" depends on FB_TFT help - Framebuffer support for the AGM1264K-FL LCD display (two Samsung KS0108 compatable chips) + Framebuffer support for the AGM1264K-FL LCD display (two Samsung KS0108 compatible chips) config FB_TFT_BD663474 tristate "FB driver for the BD663474 LCD Controller" @@ -38,6 +39,12 @@ config FB_TFT_HX8353D help Generic Framebuffer support for HX8353D +config FB_TFT_HX8357D + tristate "FB driver for the HX8357D LCD Controller" + depends on FB_TFT + help + Generic Framebuffer support for HX8357D + config FB_TFT_ILI9163 tristate "FB driver for the ILI9163 LCD Controller" depends on FB_TFT @@ -110,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 @@ -134,6 +153,16 @@ config FB_TFT_ST7735R help Generic Framebuffer support for ST7735R +config FB_TFT_ST7789V + tristate "FB driver for the ST7789V LCD Controller" + depends on FB_TFT + help + This enables generic framebuffer support for the Sitronix ST7789V + display controller. The controller is intended for small color + displays with a resolution of up to 320x240 pixels. + + Say Y if you have such a display that utilizes this controller. + config FB_TFT_TINYLCD tristate "FB driver for tinylcd.com display" depends on FB_TFT @@ -146,6 +175,12 @@ config FB_TFT_TLS8204 help Generic Framebuffer support for TLS8204 +config FB_TFT_UC1611 + tristate "FB driver for the UC1611 LCD controller" + depends on FB_TFT + help + Generic Framebuffer support for UC1611 + config FB_TFT_UC1701 tristate "FB driver for the UC1701 LCD Controller" depends on FB_TFT diff --git a/drivers/staging/fbtft/Makefile b/drivers/staging/fbtft/Makefile index 9e73beee23f4b..2725ea9a4afc7 100644 --- a/drivers/staging/fbtft/Makefile +++ b/drivers/staging/fbtft/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_FB_TFT_BD663474) += fb_bd663474.o obj-$(CONFIG_FB_TFT_HX8340BN) += fb_hx8340bn.o obj-$(CONFIG_FB_TFT_HX8347D) += fb_hx8347d.o obj-$(CONFIG_FB_TFT_HX8353D) += fb_hx8353d.o +obj-$(CONFIG_FB_TFT_HX8357D) += fb_hx8357d.o obj-$(CONFIG_FB_TFT_ILI9163) += fb_ili9163.o obj-$(CONFIG_FB_TFT_ILI9320) += fb_ili9320.o obj-$(CONFIG_FB_TFT_ILI9325) += fb_ili9325.o @@ -20,12 +21,16 @@ 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 +obj-$(CONFIG_FB_TFT_ST7789V) += fb_st7789v.o obj-$(CONFIG_FB_TFT_TINYLCD) += fb_tinylcd.o obj-$(CONFIG_FB_TFT_TLS8204) += fb_tls8204.o +obj-$(CONFIG_FB_TFT_UC1611) += fb_uc1611.o obj-$(CONFIG_FB_TFT_UC1701) += fb_uc1701.o obj-$(CONFIG_FB_TFT_UPD161704) += fb_upd161704.o obj-$(CONFIG_FB_TFT_WATTEROTT) += fb_watterott.o diff --git a/drivers/staging/fbtft/fb_agm1264k-fl.c b/drivers/staging/fbtft/fb_agm1264k-fl.c index 8f5af1db852c2..f87fc0cff42b9 100644 --- a/drivers/staging/fbtft/fb_agm1264k-fl.c +++ b/drivers/staging/fbtft/fb_agm1264k-fl.c @@ -41,8 +41,7 @@ #define CS0 gpio.aux[0] #define CS1 gpio.aux[1] - -/* diffusing error (“Floyd-Steinberg”) */ +/* diffusing error (Floyd-Steinberg) */ #define DIFFUSING_MATRIX_WIDTH 2 #define DIFFUSING_MATRIX_HEIGHT 2 @@ -75,8 +74,6 @@ static int init_display(struct fbtft_par *par) { u8 i; - fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); - par->fbtftops.reset(par); for (i = 0; i < 2; ++i) { @@ -94,7 +91,7 @@ static void reset(struct fbtft_par *par) if (par->gpio.reset == -1) return; - fbtft_dev_dbg(DEBUG_RESET, par, par->info->device, "%s()\n", __func__); + dev_dbg(par->info->device, "%s()\n", __func__); gpio_set_value(par->gpio.reset, 0); udelay(20); @@ -107,7 +104,7 @@ static int verify_gpios(struct fbtft_par *par) { int i; - fbtft_dev_dbg(DEBUG_VERIFY_GPIOS, par, par->info->device, + dev_dbg(par->info->device, "%s()\n", __func__); if (par->EPIN < 0) { @@ -145,7 +142,7 @@ static int verify_gpios(struct fbtft_par *par) static unsigned long request_gpios_match(struct fbtft_par *par, const struct fbtft_gpio *gpio) { - fbtft_dev_dbg(DEBUG_REQUEST_GPIOS_MATCH, par, par->info->device, + dev_dbg(par->info->device, "%s('%s')\n", __func__, gpio->name); if (strcasecmp(gpio->name, "wr") == 0) { @@ -180,7 +177,7 @@ static void write_reg8_bus8(struct fbtft_par *par, int len, ...) { va_list args; int i, ret; - u8 *buf = (u8 *)par->buf; + u8 *buf = par->buf; if (unlikely(par->debug & DEBUG_WRITE_REGISTER)) { va_start(args, len); @@ -245,10 +242,6 @@ static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) addr_win.ys_page = ys / 8; addr_win.xe = xe; addr_win.ye_page = ye / 8; - - fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, - "%s(xs=%d, ys_page=%d, xe=%d, ye_page=%d)\n", __func__, - addr_win.xs, addr_win.ys_page, addr_win.xe, addr_win.ye_page); } static void @@ -279,14 +272,12 @@ 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; - fbtft_par_dbg(DEBUG_WRITE_VMEM, par, "%s()\n", __func__); - /* converting to grayscale16 */ for (x = 0; x < par->info->var.xres; ++x) for (y = 0; y < par->info->var.yres; ++y) { @@ -420,7 +411,6 @@ static int write(struct fbtft_par *par, void *buf, size_t len) gpio_set_value(par->RW, 0); /* set write mode */ - while (len--) { u8 i, data; @@ -456,6 +446,7 @@ static struct fbtft_display display = { .write_vmem = write_vmem, }, }; + FBTFT_REGISTER_DRIVER(DRVNAME, "displaytronic,fb_agm1264k-fl", &display); MODULE_ALIAS("platform:" DRVNAME); diff --git a/drivers/staging/fbtft/fb_bd663474.c b/drivers/staging/fbtft/fb_bd663474.c index 17a2162a7e5b1..6010e6cbbd723 100644 --- a/drivers/staging/fbtft/fb_bd663474.c +++ b/drivers/staging/fbtft/fb_bd663474.c @@ -16,10 +16,6 @@ * 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 @@ -37,8 +33,6 @@ static int init_display(struct fbtft_par *par) { - fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); - if (par->gpio.cs != -1) gpio_set_value(par->gpio.cs, 0); /* Activate chip */ @@ -122,8 +116,6 @@ static int init_display(struct fbtft_par *par) static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) { - fbtft_par_dbg(DEBUG_SET_ADDR_WIN, par, - "%s(xs=%d, ys=%d, xe=%d, ye=%d)\n", __func__, xs, ys, xe, ye); switch (par->info->var.rotate) { /* R200h = Horizontal GRAM Start Address */ /* R201h = Vertical GRAM Start Address */ @@ -149,8 +141,6 @@ static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye) static int set_var(struct fbtft_par *par) { - fbtft_par_dbg(DEBUG_INIT_DISPLAY, par, "%s()\n", __func__); - switch (par->info->var.rotate) { /* AM: GRAM update direction */ case 0: @@ -181,6 +171,7 @@ static struct fbtft_display display = { .set_var = set_var, }, }; + FBTFT_REGISTER_DRIVER(DRVNAME, "hitachi,bd663474", &display); MODULE_ALIAS("spi:" DRVNAME); diff --git a/drivers/staging/fbtft/fb_hx8340bn.c b/drivers/staging/fbtft/fb_hx8340bn.c index 54528aa0c0efe..9970ed74bb38e 100644 --- a/drivers/staging/fbtft/fb_hx8340bn.c +++ b/drivers/staging/fbtft/fb_hx8340bn.c @@ -17,10 +17,6 @@ * 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 @@ -29,6 +25,7 @@ #include #include #include +#include